diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca3d87b12..0c9266328 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ jobs: name: NetBSD Build, Check, and Test runs-on: ubuntu-latest env: - PKGSRC_BRANCH: 2024Q1 + PKGSRC_BRANCH: 2024Q2 steps: - uses: actions/checkout@v4 - name: Build, Check, and Test @@ -18,13 +18,11 @@ jobs: usesh: true copyback: false prepare: | - PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin - pkgin -y in gmake git bash python311 - pkgin -y in libxml2 perl zstd - /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz - /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz + PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/10.0_2024Q2/All" /usr/sbin/pkg_add pkgin + pkgin -y in gmake git bash python311 llvm clang ln -s /usr/pkg/bin/python3.11 /usr/bin/python3 run: | + set -e -x git config --global --add safe.directory $(pwd) gmake release ./odin version @@ -91,13 +89,13 @@ jobs: - name: Download LLVM (MacOS Intel) if: matrix.os == 'macos-13' run: | - brew install llvm@17 + brew install llvm@17 lua@5.4 echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH - name: Download LLVM (MacOS ARM) if: matrix.os == 'macos-14' run: | - brew install llvm@17 wasmtime + brew install llvm@17 wasmtime lua@5.4 echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH - name: Build Odin @@ -207,6 +205,7 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + copy vendor\lua\5.4\windows\*.dll . odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false - name: Odin internals tests shell: cmd diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0857e99ad..e4bc5d81c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -50,8 +50,8 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 17 - echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH + sudo ./llvm.sh 18 + echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH - name: build odin run: make nightly - name: Odin run @@ -82,8 +82,8 @@ jobs: - uses: actions/checkout@v4 - name: Download LLVM and setup PATH run: | - brew install llvm@17 dylibbundler - echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH + brew install llvm@18 dylibbundler + echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to # not link with libunwind bundled with LLVM but link with libunwind on the system. @@ -116,8 +116,8 @@ jobs: - uses: actions/checkout@v4 - name: Download LLVM and setup PATH run: | - brew install llvm@17 dylibbundler - echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH + brew install llvm@18 dylibbundler + echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to # not link with libunwind bundled with LLVM but link with libunwind on the system. diff --git a/.gitignore b/.gitignore index 750145c58..42b2d3d95 100644 --- a/.gitignore +++ b/.gitignore @@ -24,38 +24,6 @@ bld/ ![Cc]ore/[Ll]og/ tests/documentation/verify/ tests/documentation/all.odin-doc -tests/internal/test_map -tests/internal/test_pow -tests/internal/test_rtti -tests/core/test_base64 -tests/core/test_cbor -tests/core/test_core_compress -tests/core/test_core_container -tests/core/test_core_filepath -tests/core/test_core_fmt -tests/core/test_core_i18n -tests/core/test_core_image -tests/core/test_core_libc -tests/core/test_core_match -tests/core/test_core_math -tests/core/test_core_net -tests/core/test_core_os_exit -tests/core/test_core_reflect -tests/core/test_core_strings -tests/core/test_core_time -tests/core/test_crypto -tests/core/test_hash -tests/core/test_hex -tests/core/test_hxa -tests/core/test_json -tests/core/test_linalg_glsl_math -tests/core/test_noise -tests/core/test_varint -tests/core/test_xml -tests/core/test_core_slice -tests/core/test_core_thread -tests/core/test_core_runtime -tests/vendor/vendor_botan # Visual Studio 2015 cache/options directory .vs/ # Visual Studio Code options directory @@ -63,6 +31,7 @@ tests/vendor/vendor_botan # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ demo +benchmark # MSTest test Results [Tt]est[Rr]esult*/ diff --git a/LLVM-C.dll b/LLVM-C.dll index 6393857b4..ee03a2acd 100644 Binary files a/LLVM-C.dll and b/LLVM-C.dll differ diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 8a16ca40e..37a42b904 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -38,9 +38,12 @@ count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- -overflow_add :: proc(lhs, rhs: $T) -> (T, bool) --- -overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) --- -overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) --- +overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- +overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- +overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- + +add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- +sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 550daed3b..e2490051e 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -66,7 +66,7 @@ Type_Info_Named :: struct { name: string, base: ^Type_Info, pkg: string, - loc: Source_Code_Location, + loc: ^Source_Code_Location, } Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness} Type_Info_Rune :: struct {} @@ -112,23 +112,32 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu } 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, +Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8] +Type_Info_Struct_Flag :: enum u8 { + packed = 0, + raw_union = 1, + no_copy = 2, + align = 3, +} - equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set +Type_Info_Struct :: struct { + // Slice these with `field_count` + types: [^]^Type_Info `fmt:"v,field_count"`, + names: [^]string `fmt:"v,field_count"`, + offsets: [^]uintptr `fmt:"v,field_count"`, + usings: [^]bool `fmt:"v,field_count"`, + tags: [^]string `fmt:"v,field_count"`, + + field_count: i32, + + flags: Type_Info_Struct_Flags, // These are only set iff this structure is an SOA structure soa_kind: Type_Info_Struct_Soa_Kind, + soa_len: i32, soa_base_type: ^Type_Info, - soa_len: int, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set } Type_Info_Union :: struct { variants: []^Type_Info, @@ -142,9 +151,9 @@ Type_Info_Union :: struct { shared_nil: bool, } Type_Info_Enum :: struct { - base: ^Type_Info, - names: []string, - values: []Type_Info_Enum_Value, + base: ^Type_Info, + names: []string, + values: []Type_Info_Enum_Value, } Type_Info_Map :: struct { key: ^Type_Info, @@ -187,11 +196,12 @@ Type_Info_Soa_Pointer :: struct { } Type_Info_Bit_Field :: struct { backing_type: ^Type_Info, - names: []string, - types: []^Type_Info, - bit_sizes: []uintptr, - bit_offsets: []uintptr, - tags: []string, + names: [^]string `fmt:"v,field_count"`, + types: [^]^Type_Info `fmt:"v,field_count"`, + bit_sizes: [^]uintptr `fmt:"v,field_count"`, + bit_offsets: [^]uintptr `fmt:"v,field_count"`, + tags: [^]string `fmt:"v,field_count"`, + field_count: int, } Type_Info_Flag :: enum u8 { @@ -299,6 +309,8 @@ when ODIN_OS == .Windows { Thread_Detach = 3, } dll_forward_reason: DLL_Forward_Reason + + dll_instance: rawptr } // IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it) @@ -513,11 +525,12 @@ Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} Linux, Essence, FreeBSD, - Haiku, OpenBSD, NetBSD, + Haiku, WASI, JS, + Orca, Freestanding, } */ @@ -577,7 +590,7 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) Memory = 1, Thread = 2, } - Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32] + Odin_Sanitizer_Flags :: distinct bit_set[Odin_Sanitizer_Flag; u32] ODIN_SANITIZER_FLAGS // is a constant */ diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index ff87316f2..38ad95be8 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -333,16 +333,23 @@ make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, alloca // 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) - array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory - 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 + err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, cap, allocator, loc) return } + +@(require_results) +_make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { + make_dynamic_array_error_loc(loc, len, cap) + array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory + data := mem_alloc_bytes(size_of_elem*cap, align_of_elem, allocator, loc) or_return + use_zero := data == nil && size_of_elem != 0 + array.data = raw_data(data) + array.len = 0 if use_zero else len + array.cap = 0 if use_zero else cap + array.allocator = allocator + 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. // @@ -440,107 +447,103 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: 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 { +_append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { - return 0, nil + return } - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += 1 - return 1, nil - } else { - if cap(array) < len(array)+1 { - // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. - cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY - // 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 + if array.cap < array.len+1 { + // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. + cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY + + // do not 'or_return' here as it could be a partial success + err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) } + if array.cap-array.len > 0 { + data := ([^]byte)(array.data) + assert(data != nil, loc=loc) + data = data[array.len*size_of_elem:] + intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem) + array.len += 1 + n = 1 + } + return } @builtin append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, true, loc=loc) + when size_of(E) == 0 { + (^Raw_Dynamic_Array)(array).len += 1 + return 1, nil + } else { + arg := arg + return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc) + } } @builtin non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, false, loc=loc) + when size_of(E) == 0 { + (^Raw_Dynamic_Array)(array).len += 1 + return 1, nil + } else { + arg := arg + return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &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 { +_append_elems :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (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(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len) + if array.cap < array.len+arg_len { + cap := 2 * array.cap + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, 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 + // do not 'or_return' here as it could be a partial success + err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) } + arg_len := arg_len + arg_len = min(array.cap-array.len, arg_len) + if arg_len > 0 { + data := ([^]byte)(array.data) + assert(data != nil, loc=loc) + data = data[array.len*size_of_elem:] + intrinsics.mem_copy(data, args, size_of_elem * arg_len) // must be mem_copy (overlapping) + array.len += arg_len + } + return arg_len, err } @builtin append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, true, loc, ..args) + when size_of(E) == 0 { + a := (^Raw_Dynamic_Array)(array) + a.len += len(args) + return len(args), nil + } else { + return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), true, loc, raw_data(args), len(args)) + } } @builtin non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, false, loc, ..args) + when size_of(E) == 0 { + a := (^Raw_Dynamic_Array)(array) + a.len += len(args) + return len(args), nil + } else { + return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), false, loc, raw_data(args), len(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) - } + return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg)) } @builtin @@ -679,7 +682,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle @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 { +assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast 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 @@ -729,11 +732,10 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { // `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 { +_reserve_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if a == nil { return nil } - a := (^Raw_Dynamic_Array)(array) if capacity <= a.cap { return nil @@ -744,15 +746,15 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := capacity * size_of(E) + old_size := a.cap * size_of_elem + new_size := capacity * size_of_elem 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 + new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } if new_data == nil && new_size > 0 { return .Out_Of_Memory @@ -765,26 +767,23 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i @builtin reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, true, loc) + return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc) } @builtin non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, false, loc) + return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), 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 { + +_resize_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if a == nil { return nil } - a := (^Raw_Dynamic_Array)(array) if length <= a.cap { if should_zero && a.len < length { - intrinsics.mem_zero(([^]E)(a.data)[a.len:], (length-a.len)*size_of(E)) + intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], (length-a.len)*size_of_elem) } a.len = max(length, 0) return nil @@ -795,15 +794,15 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := length * size_of(E) + old_size := a.cap * size_of_elem + new_size := length * size_of_elem 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 + new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } if new_data == nil && new_size > 0 { return .Out_Of_Memory @@ -815,14 +814,17 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, return nil } +// `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` @builtin resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, true, loc=loc) + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc) } @builtin non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, false, loc=loc) + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc) } /* @@ -837,10 +839,13 @@ non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: i 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 _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc) +} + +_shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + if a == nil { return } - a := (^Raw_Dynamic_Array)(array) new_cap := new_cap if new_cap >= 0 else a.len @@ -853,10 +858,10 @@ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #call } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := new_cap * size_of(E) + old_size := a.cap * size_of_elem + new_size := new_cap * size_of_elem - new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return + new_data := mem_resize(a.data, old_size, new_size, align_of_elem, a.allocator, loc) or_return a.data = raw_data(new_data) a.len = min(new_cap, a.len) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index f1b17cbef..7f7f5f086 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -352,7 +352,7 @@ non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args } -_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: []E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return } diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin index 5ad155400..3dded7716 100644 --- a/base/runtime/dynamic_map_internal.odin +++ b/base/runtime/dynamic_map_internal.odin @@ -577,7 +577,7 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf @(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 { +map_reserve_dynamic :: #force_no_inline 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) @@ -641,7 +641,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ @(require_results) -map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { +map_shrink_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { if m.allocator.procedure == nil { m.allocator = context.allocator } @@ -688,7 +688,7 @@ map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_I } @(require_results) -map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error { +map_free_dynamic :: #force_no_inline proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> 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) @@ -700,7 +700,7 @@ map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_loc } @(require_results) -map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { +map_lookup_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { if map_len(m) == 0 { return 0, false } @@ -723,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, } } @(require_results) -map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { +map_exists_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { if map_len(m) == 0 { return false } @@ -749,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, @(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) { +map_erase_dynamic :: #force_no_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 diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index 7020e9ea8..1e2dcc21a 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -10,8 +10,9 @@ when ODIN_BUILD_MODE == .Dynamic { DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { context = default_context() - // Populate Windows DLL-specific global + // Populate Windows DLL-specific globals dll_forward_reason = DLL_Forward_Reason(fdwReason) + dll_instance = hinstDLL switch dll_forward_reason { case .Process_Attach: diff --git a/base/runtime/os_specific_darwin.odin b/base/runtime/os_specific_darwin.odin index 61c17a597..1bf29e785 100644 --- a/base/runtime/os_specific_darwin.odin +++ b/base/runtime/os_specific_darwin.odin @@ -5,11 +5,24 @@ package runtime import "base:intrinsics" _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - WRITE :: 0x2000004 STDERR :: 2 - ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data))) - if ret < 0 { - return 0, _OS_Errno(-ret) + when ODIN_NO_CRT { + WRITE :: 0x2000004 + ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data))) + if ret < 0 { + return 0, _OS_Errno(-ret) + } + return int(ret), 0 + } else { + foreign { + write :: proc(handle: i32, buffer: [^]byte, count: uint) -> int --- + __error :: proc() -> ^i32 --- + } + + if ret := write(STDERR, raw_data(data), len(data)); ret >= 0 { + return int(ret), 0 + } + + return 0, _OS_Errno(__error()^) } - return int(ret), 0 } diff --git a/base/runtime/print.odin b/base/runtime/print.odin index ed5893e15..45f6f01ef 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -262,7 +262,7 @@ print_typeid :: #force_no_inline proc "contextless" (id: typeid) { } } -@(optimization_mode="size") +@(optimization_mode="favor_size") print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { if ti == nil { print_string("nil") @@ -401,15 +401,16 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { } print_string("struct ") - if info.is_packed { print_string("#packed ") } - if info.is_raw_union { print_string("#raw_union ") } - if info.custom_align { + if .packed in info.flags { print_string("#packed ") } + if .raw_union in info.flags { print_string("#raw_union ") } + if .no_copy in info.flags { print_string("#no_copy ") } + if .align in info.flags { print_string("#align(") print_u64(u64(ti.align)) print_string(") ") } print_byte('{') - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { print_string(", ") } print_string(name) print_string(": ") @@ -469,7 +470,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { print_string("bit_field ") print_type(info.backing_type) print_string(" {") - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { print_string(", ") } print_string(name) print_string(": ") diff --git a/base/runtime/wasm_allocator.odin b/base/runtime/wasm_allocator.odin index f4b399c47..6bafaa489 100644 --- a/base/runtime/wasm_allocator.odin +++ b/base/runtime/wasm_allocator.odin @@ -297,7 +297,8 @@ lock :: proc(a: ^WASM_Allocator) { return } - assert(intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) != 0) + ret := intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) + assert(ret != 0) intrinsics.cpu_relax() } } diff --git a/bin/llvm/windows/LLVM-C.lib b/bin/llvm/windows/LLVM-C.lib index bd57594f3..0e5b6c624 100644 Binary files a/bin/llvm/windows/LLVM-C.lib and b/bin/llvm/windows/LLVM-C.lib differ diff --git a/bin/llvm/windows/clang_rt.asan-x86_64.lib b/bin/llvm/windows/clang_rt.asan-x86_64.lib index 9b6971395..0b209dc8d 100644 Binary files a/bin/llvm/windows/clang_rt.asan-x86_64.lib and b/bin/llvm/windows/clang_rt.asan-x86_64.lib differ diff --git a/build.bat b/build.bat index 050789bbc..bddde49c8 100644 --- a/build.bat +++ b/build.bat @@ -48,6 +48,9 @@ if "%2" == "1" ( set odin_version_raw="dev-%curr_year%-%curr_month%" set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF +rem Parse source code as utf-8 even on shift-jis and other codepages +rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 +set compiler_flags= %compiler_flags% /utf-8 set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" if not exist .git\ goto skip_git_hash @@ -111,7 +114,7 @@ call build_vendor.bat if %errorlevel% neq 0 goto end_of_build rem If the demo doesn't run for you and your CPU is more than a decade old, try -microarch:native -if %release_mode% EQU 0 odin run examples/demo -- Hellope World +if %release_mode% EQU 0 odin run examples/demo -vet -strict-style -- Hellope World del *.obj > NUL 2> NUL diff --git a/build_odin.sh b/build_odin.sh index d2f865e24..125b9335a 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -144,7 +144,7 @@ build_odin() { } run_demo() { - ./odin run examples/demo/demo.odin -file -- Hellope World + ./odin run examples/demo -vet -strict-style -- Hellope World } if [ $# -eq 0 ]; then diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 208949fd8..7cbf092ac 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -1167,3 +1167,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc return subslices[:] } + +// alias returns true iff a and b have a non-zero length, and any part of +// a overlaps with b. +alias :: proc "contextless" (a, b: []byte) -> bool { + a_len, b_len := len(a), len(b) + if a_len == 0 || b_len == 0 { + return false + } + + a_start, b_start := uintptr(raw_data(a)), uintptr(raw_data(b)) + a_end, b_end := a_start + uintptr(a_len-1), b_start + uintptr(b_len-1) + + return a_start <= b_end && b_start <= a_end +} + +// alias_inexactly returns true iff a and b have a non-zero length, +// the base pointer of a and b are NOT equal, and any part of a overlaps +// with b (ie: `alias(a, b)` with an exception that returns false for +// `a == b`, `b = a[:len(a)-69]` and similar conditions). +alias_inexactly :: proc "contextless" (a, b: []byte) -> bool { + if raw_data(a) == raw_data(b) { + return false + } + return alias(a, b) +} diff --git a/core/compress/common.odin b/core/compress/common.odin index 47ba45e88..242538e78 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -186,7 +186,7 @@ input_size_from_stream :: proc(z: ^Context_Stream_Input) -> (res: i64, err: Erro input_size :: proc{input_size_from_memory, input_size_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int) -> (res: []u8, err: io.Error) { #no_bounds_check { if len(z.input_data) >= size { @@ -203,7 +203,7 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) { // TODO: REMOVE ALL USE OF context.temp_allocator here // there is literally no need for it @@ -214,13 +214,13 @@ read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int read_slice :: proc{read_slice_from_memory, read_slice_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) { b := read_slice(z, size_of(T)) or_return return (^T)(&b[0])^, nil } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, err: io.Error) { #no_bounds_check { if len(z.input_data) >= 1 { @@ -232,7 +232,7 @@ read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, return 0, .EOF } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, err: io.Error) { b := read_slice_from_stream(z, 1) or_return return b[0], nil @@ -242,7 +242,7 @@ read_u8 :: proc{read_u8_from_memory, read_u8_from_stream} // You would typically only use this at the end of Inflate, to drain bits from the code buffer // preferentially. -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: io.Error) { if z.num_bits >= 8 { res = u8(read_bits_no_refill_lsb(z, 8)) @@ -257,7 +257,7 @@ read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: i return } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid) -> (res: T, err: io.Error) { size :: size_of(T) @@ -275,7 +275,7 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) { size :: size_of(T) @@ -293,7 +293,7 @@ peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) { size :: size_of(T) @@ -317,7 +317,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid return res, .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) { size :: size_of(T) @@ -352,14 +352,14 @@ peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_off // Sliding window read back -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) { // Look back into the sliding window. return z.output.buf[z.bytes_written - offset], .None } // Generalized bit reader LSB -@(optimization_mode="speed") +@(optimization_mode="favor_size") refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := i8(48)) { refill := u64(width) b := u64(0) @@ -385,7 +385,7 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := } // Generalized bit reader LSB -@(optimization_mode="speed") +@(optimization_mode="favor_size") refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) { refill := u64(width) @@ -414,13 +414,13 @@ refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) { refill_lsb :: proc{refill_lsb_from_memory, refill_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) { z.code_buffer >>= width z.num_bits -= u64(width) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) { z.code_buffer >>= width z.num_bits -= u64(width) @@ -428,7 +428,7 @@ consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, wid consume_bits_lsb :: proc{consume_bits_lsb_from_memory, consume_bits_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { if z.num_bits < u64(width) { refill_lsb(z) @@ -436,7 +436,7 @@ peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: return u32(z.code_buffer &~ (~u64(0) << width)) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { if z.num_bits < u64(width) { refill_lsb(z) @@ -446,13 +446,13 @@ peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: peek_bits_lsb :: proc{peek_bits_lsb_from_memory, peek_bits_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { assert(z.num_bits >= u64(width)) return u32(z.code_buffer &~ (~u64(0) << width)) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { assert(z.num_bits >= u64(width)) return u32(z.code_buffer &~ (~u64(0) << width)) @@ -460,14 +460,14 @@ peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp peek_bits_no_refill_lsb :: proc{peek_bits_no_refill_lsb_from_memory, peek_bits_no_refill_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { k := #force_inline peek_bits_lsb(z, width) #force_inline consume_bits_lsb(z, width) return k } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { k := peek_bits_lsb(z, width) consume_bits_lsb(z, width) @@ -476,14 +476,14 @@ read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: read_bits_lsb :: proc{read_bits_lsb_from_memory, read_bits_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 { k := #force_inline peek_bits_no_refill_lsb(z, width) #force_inline consume_bits_lsb(z, width) return k } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 { k := peek_bits_no_refill_lsb(z, width) consume_bits_lsb(z, width) @@ -493,14 +493,14 @@ read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp read_bits_no_refill_lsb :: proc{read_bits_no_refill_lsb_from_memory, read_bits_no_refill_lsb_from_stream} -@(optimization_mode="speed") +@(optimization_mode="favor_size") discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) { discard := u8(z.num_bits & 7) #force_inline consume_bits_lsb(z, discard) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) { discard := u8(z.num_bits & 7) consume_bits_lsb(z, discard) diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index b7f381f2b..c7ae9e9c8 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -120,7 +120,7 @@ Huffman_Table :: struct { } // Implementation starts here -@(optimization_mode="speed") +@(optimization_mode="favor_size") z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) { assert(bits <= 16) // NOTE: Can optimize with llvm.bitreverse.i64 or some bit twiddling @@ -136,7 +136,7 @@ z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) { } -@(optimization_mode="speed") +@(optimization_mode="favor_size") grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) { /* That we get here at all means that we didn't pass an expected output size, @@ -154,7 +154,7 @@ grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) { TODO: Make these return compress.Error. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_check { /* Resize if needed. @@ -173,7 +173,7 @@ write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_ch return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check { /* TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it @@ -201,7 +201,7 @@ repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") repl_bytes :: proc(z: ^$C, count: u16, distance: u16) -> (err: io.Error) { /* TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it @@ -234,8 +234,8 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T return new(Huffman_Table, allocator), nil } -@(optimization_mode="speed") -build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { +@(optimization_mode="favor_size") +build_huffman :: #force_no_inline proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { sizes: [HUFFMAN_MAX_BITS+1]int next_code: [HUFFMAN_MAX_BITS+1]int @@ -293,7 +293,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { return nil } -@(optimization_mode="speed") +@(optimization_mode="favor_size") decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { code := u16(compress.peek_bits_lsb(z,16)) @@ -324,7 +324,7 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro return r, nil } -@(optimization_mode="speed") +@(optimization_mode="favor_size") decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { if z.num_bits < 16 { if z.num_bits > 63 { @@ -344,7 +344,7 @@ decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bo return decode_huffman_slowpath(z, t) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check { #no_bounds_check for { value, e := decode_huffman(z, z_repeat) @@ -413,7 +413,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := false, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check { /* ctx.output must be a bytes.Buffer for now. We'll add a separate implementation that writes to a stream. @@ -486,7 +486,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f // TODO: Check alignment of reserve/resize. -@(optimization_mode="speed") +@(optimization_mode="favor_size") inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check { context.allocator = allocator expected_output_size := expected_output_size @@ -670,4 +670,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals return inflate_raw(&ctx, expected_output_size=expected_output_size) } -inflate :: proc{inflate_from_context, inflate_from_byte_array} +inflate :: proc{inflate_from_context, inflate_from_byte_array} \ No newline at end of file diff --git a/core/container/intrusive/list/doc.odin b/core/container/intrusive/list/doc.odin new file mode 100644 index 000000000..1a5a12f49 --- /dev/null +++ b/core/container/intrusive/list/doc.odin @@ -0,0 +1,46 @@ +/* +Package list implements an intrusive doubly-linked list. + +An intrusive container requires a `Node` to be embedded in your own structure, like this: + + My_String :: struct { + node: list.Node, + value: string, + } + +Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed: + + My_String :: struct { + using node: list.Node, + value: string, + } + +Here is a full example: + + package test + + import "core:fmt" + import "core:container/intrusive/list" + + main :: proc() { + l: list.List + + one := My_String{value="Hello"} + two := My_String{value="World"} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + iter := list.iterator_head(l, My_String, "node") + for s in list.iterate_next(&iter) { + fmt.println(s.value) + } + } + + My_String :: struct { + node: list.Node, + value: string, + } + +*/ +package container_intrusive_list diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin index 1a3175002..5b29efb22 100644 --- a/core/container/intrusive/list/intrusive_list.odin +++ b/core/container/intrusive/list/intrusive_list.odin @@ -18,11 +18,18 @@ List :: struct { tail: ^Node, } - +// The list link you must include in your own structure. Node :: struct { prev, next: ^Node, } +/* +Inserts a new element at the front of the list with O(1) time complexity. + +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure +*/ push_front :: proc "contextless" (list: ^List, node: ^Node) { if list.head != nil { list.head.prev = node @@ -33,7 +40,13 @@ push_front :: proc "contextless" (list: ^List, node: ^Node) { node.prev, node.next = nil, nil } } +/* +Inserts a new element at the back of the list with O(1) time complexity. +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure +*/ push_back :: proc "contextless" (list: ^List, node: ^Node) { if list.tail != nil { list.tail.next = node @@ -45,6 +58,13 @@ push_back :: proc "contextless" (list: ^List, node: ^Node) { } } +/* +Removes an element from a list with O(1) time complexity. + +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure to be removed +*/ remove :: proc "contextless" (list: ^List, node: ^Node) { if node != nil { if node.next != nil { @@ -61,7 +81,13 @@ remove :: proc "contextless" (list: ^List, node: ^Node) { } } } +/* +Removes from the given list all elements that satisfy a condition with O(N) time complexity. +**Inputs** +- list: The container list +- to_erase: The condition procedure. It should return `true` if a node should be removed, `false` otherwise +*/ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { for node := list.head; node != nil; { next := node.next @@ -82,7 +108,13 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { node = next } } +/* +Removes from the given list all elements that satisfy a condition with O(N) time complexity. +**Inputs** +- list: The container list +- to_erase: The _contextless_ condition procedure. It should return `true` if a node should be removed, `false` otherwise +*/ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) { for node := list.head; node != nil; { next := node.next @@ -104,12 +136,26 @@ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^N } } +/* +Checks whether the given list does not contain any element. +**Inputs** +- list: The container list +**Returns** `true` if `list` is empty, `false` otherwise +*/ is_empty :: proc "contextless" (list: ^List) -> bool { return list.head == nil } +/* +Removes and returns the element at the front of the list with O(1) time complexity. + +**Inputs** +- list: The container list + +**Returns** The node member of the user-defined element structure, or `nil` if the list is empty +*/ pop_front :: proc "contextless" (list: ^List) -> ^Node { link := list.head if link == nil { @@ -130,6 +176,14 @@ pop_front :: proc "contextless" (list: ^List) -> ^Node { return link } +/* +Removes and returns the element at the back of the list with O(1) time complexity. + +**Inputs** +- list: The container list + +**Returns** The node member of the user-defined element structure, or `nil` if the list is empty +*/ pop_back :: proc "contextless" (list: ^List) -> ^Node { link := list.tail if link == nil { @@ -151,29 +205,102 @@ pop_back :: proc "contextless" (list: ^List) -> ^Node { } + Iterator :: struct($T: typeid) { curr: ^Node, offset: uintptr, } +/* +Creates an iterator pointing at the head of the given list. For an example, see `iterate_next`. + +**Inputs** +- list: The container list +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at the head of `list` + +*/ iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {list.head, offset_of_by_string(T, field_name)} } +/* +Creates an iterator pointing at the tail of the given list. For an example, see `iterate_prev`. +**Inputs** +- list: The container list +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at the tail of `list` + +*/ iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {list.tail, offset_of_by_string(T, field_name)} } +/* +Creates an iterator pointing at the specified node of a list. +**Inputs** +- node: a list node +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at `node` + +*/ iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {node, offset_of_by_string(T, field_name)} } +/* +Retrieves the next element in a list and advances the iterator. + +**Inputs** +- it: The iterator + +**Returns** +- ptr: The next list element +- ok: `true` if the element is valid (the iterator could advance), `false` otherwise + +Example: + + import "core:fmt" + import "core:container/intrusive/list" + + iterate_next_example :: proc() { + l: list.List + + one := My_Struct{value=1} + two := My_Struct{value=2} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + it := list.iterator_head(l, My_Struct, "node") + for num in list.iterate_next(&it) { + fmt.println(num.value) + } + } + + My_Struct :: struct { + node : list.Node, + value: int, + } + +Output: + + 1 + 2 + +*/ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { node := it.curr if node == nil { @@ -183,7 +310,47 @@ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { return (^T)(uintptr(node) - it.offset), true } +/* +Retrieves the previous element in a list and recede the iterator. +**Inputs** +- it: The iterator + +**Returns** +- ptr: The previous list element +- ok: `true` if the element is valid (the iterator could recede), `false` otherwise + +Example: + + import "core:fmt" + import "core:container/intrusive/list" + + iterate_next_example :: proc() { + l: list.List + + one := My_Struct{value=1} + two := My_Struct{value=2} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + it := list.iterator_tail(l, My_Struct, "node") + for num in list.iterate_prev(&it) { + fmt.println(num.value) + } + } + + My_Struct :: struct { + node : list.Node, + value: int, + } + +Output: + + 2 + 1 + +*/ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { node := it.curr if node == nil { @@ -192,4 +359,4 @@ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { it.curr = node.prev return (^T)(uintptr(node) - it.offset), true -} \ No newline at end of file +} diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index e7a60dde0..f83a5f2b7 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -95,11 +95,11 @@ front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { } back :: proc(q: ^$Q/Queue($T)) -> T { - idx := (q.offset+uint(q.len))%builtin.len(q.data) + idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return q.data[idx] } back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { - idx := (q.offset+uint(q.len))%builtin.len(q.data) + idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return &q.data[idx] } diff --git a/core/crypto/_aes/ct64/api.odin b/core/crypto/_aes/ct64/api.odin index ae624971c..f57a630b1 100644 --- a/core/crypto/_aes/ct64/api.odin +++ b/core/crypto/_aes/ct64/api.odin @@ -7,9 +7,8 @@ STRIDE :: 4 // Context is a keyed AES (ECB) instance. Context :: struct { - _sk_exp: [120]u64, - _num_rounds: int, - _is_initialized: bool, + _sk_exp: [120]u64, + _num_rounds: int, } // init initializes a context for AES with the provided key. @@ -18,13 +17,10 @@ init :: proc(ctx: ^Context, key: []byte) { ctx._num_rounds = keysched(skey[:], key) skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds) - ctx._is_initialized = true } // encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`. encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { - assert(ctx._is_initialized) - q: [8]u64 load_blockx1(&q, src) _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) @@ -33,8 +29,6 @@ encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { // encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`. decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { - assert(ctx._is_initialized) - q: [8]u64 load_blockx1(&q, src) _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) @@ -43,8 +37,6 @@ decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { // encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`. encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { - assert(ctx._is_initialized) - q: [8]u64 = --- src, dst := src, dst @@ -67,8 +59,6 @@ encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { // decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`. decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { - assert(ctx._is_initialized) - q: [8]u64 = --- src, dst := src, dst diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin new file mode 100644 index 000000000..5cb5a68bb --- /dev/null +++ b/core/crypto/_aes/hw_intel/api.odin @@ -0,0 +1,43 @@ +//+build amd64 +package aes_hw_intel + +import "core:sys/info" + +// is_supporte returns true iff hardware accelerated AES +// is supported. +is_supported :: proc "contextless" () -> bool { + features, ok := info.cpu_features.? + if !ok { + return false + } + + // Note: Everything with AES-NI and PCLMULQDQ has support for + // the required SSE extxtensions. + req_features :: info.CPU_Features{ + .sse2, + .ssse3, + .sse41, + .aes, + .pclmulqdq, + } + return features >= req_features +} + +// Context is a keyed AES (ECB) instance. +Context :: struct { + // Note: The ideal thing to do is for the expanded round keys to be + // arrays of `__m128i`, however that implies alignment (or using AVX). + // + // All the people using e-waste processors that don't support an + // insturction set that has been around for over 10 years are why + // we can't have nice things. + _sk_exp_enc: [15][16]byte, + _sk_exp_dec: [15][16]byte, + _num_rounds: int, +} + +// init initializes a context for AES with the provided key. +init :: proc(ctx: ^Context, key: []byte) { + keysched(ctx, key) +} + diff --git a/core/crypto/_aes/hw_intel/ghash.odin b/core/crypto/_aes/hw_intel/ghash.odin new file mode 100644 index 000000000..9a5208523 --- /dev/null +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -0,0 +1,281 @@ +// Copyright (c) 2017 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//+build amd64 +package aes_hw_intel + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:simd" +import "core:simd/x86" + +@(private = "file") +GHASH_STRIDE_HW :: 4 +@(private = "file") +GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE + +// GHASH is defined over elements of GF(2^128) with "full little-endian" +// representation: leftmost byte is least significant, and, within each +// byte, leftmost _bit_ is least significant. The natural ordering in +// x86 is "mixed little-endian": bytes are ordered from least to most +// significant, but bits within a byte are in most-to-least significant +// order. Going to full little-endian representation would require +// reversing bits within each byte, which is doable but expensive. +// +// Instead, we go to full big-endian representation, by swapping bytes +// around, which is done with a single _mm_shuffle_epi8() opcode (it +// comes with SSSE3; all CPU that offer pclmulqdq also have SSSE3). We +// can use a full big-endian representation because in a carryless +// multiplication, we have a nice bit reversal property: +// +// rev_128(x) * rev_128(y) = rev_255(x * y) +// +// So by using full big-endian, we still get the right result, except +// that it is right-shifted by 1 bit. The left-shift is relatively +// inexpensive, and it can be mutualised. +// +// Since SSE2 opcodes do not have facilities for shitfting full 128-bit +// values with bit precision, we have to break down values into 64-bit +// chunks. We number chunks from 0 to 3 in left to right order. + +@(private = "file") +byteswap_index := transmute(x86.__m128i)simd.i8x16{ + // Note: simd.i8x16 is reverse order from x86._mm_set_epi8. + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, +} + +@(private = "file", require_results, enable_target_feature = "sse2,ssse3") +byteswap :: #force_inline proc "contextless" (x: x86.__m128i) -> x86.__m128i { + return x86._mm_shuffle_epi8(x, byteswap_index) +} + +// From a 128-bit value kw, compute kx as the XOR of the two 64-bit +// halves of kw (into the right half of kx; left half is unspecified), +// and return kx. +@(private = "file", require_results, enable_target_feature = "sse2") +bk :: #force_inline proc "contextless" (kw: x86.__m128i) -> x86.__m128i { + return x86._mm_xor_si128(kw, x86._mm_shuffle_epi32(kw, 0x0e)) +} + +// Combine two 64-bit values (k0:k1) into a 128-bit (kw) value and +// the XOR of the two values (kx), and return (kw, kx). +@(private = "file", enable_target_feature = "sse2") +pbk :: #force_inline proc "contextless" (k0, k1: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + kw := x86._mm_unpacklo_epi64(k1, k0) + kx := x86._mm_xor_si128(k0, k1) + return kw, kx +} + +// Left-shift by 1 bit a 256-bit value (in four 64-bit words). +@(private = "file", require_results, enable_target_feature = "sse2") +sl_256 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i, x86.__m128i, x86.__m128i) { + x0, x1, x2, x3 := x0, x1, x2, x3 + + x0 = x86._mm_or_si128(x86._mm_slli_epi64(x0, 1), x86._mm_srli_epi64(x1, 63)) + x1 = x86._mm_or_si128(x86._mm_slli_epi64(x1, 1), x86._mm_srli_epi64(x2, 63)) + x2 = x86._mm_or_si128(x86._mm_slli_epi64(x2, 1), x86._mm_srli_epi64(x3, 63)) + x3 = x86._mm_slli_epi64(x3, 1) + + return x0, x1, x2, x3 +} + +// Perform reduction in GF(2^128). +@(private = "file", require_results, enable_target_feature = "sse2") +reduce_f128 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + x0, x1, x2 := x0, x1, x2 + + x1 = x86._mm_xor_si128( + x1, + x86._mm_xor_si128( + x86._mm_xor_si128( + x3, + x86._mm_srli_epi64(x3, 1)), + x86._mm_xor_si128( + x86._mm_srli_epi64(x3, 2), + x86._mm_srli_epi64(x3, 7)))) + x2 = x86._mm_xor_si128( + x86._mm_xor_si128( + x2, + x86._mm_slli_epi64(x3, 63)), + x86._mm_xor_si128( + x86._mm_slli_epi64(x3, 62), + x86._mm_slli_epi64(x3, 57))) + x0 = x86._mm_xor_si128( + x0, + x86._mm_xor_si128( + x86._mm_xor_si128( + x2, + x86._mm_srli_epi64(x2, 1)), + x86._mm_xor_si128( + x86._mm_srli_epi64(x2, 2), + x86._mm_srli_epi64(x2, 7)))) + x1 = x86._mm_xor_si128( + x86._mm_xor_si128( + x1, + x86._mm_slli_epi64(x2, 63)), + x86._mm_xor_si128( + x86._mm_slli_epi64(x2, 62), + x86._mm_slli_epi64(x2, 57))) + + return x0, x1 +} + +// Square value kw in GF(2^128) into (dw,dx). +@(private = "file", require_results, enable_target_feature = "sse2,pclmul") +square_f128 :: #force_inline proc "contextless" (kw: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + z1 := x86._mm_clmulepi64_si128(kw, kw, 0x11) + z3 := x86._mm_clmulepi64_si128(kw, kw, 0x00) + z0 := x86._mm_shuffle_epi32(z1, 0x0E) + z2 := x86._mm_shuffle_epi32(z3, 0x0E) + z0, z1, z2, z3 = sl_256(z0, z1, z2, z3) + z0, z1 = reduce_f128(z0, z1, z2, z3) + return pbk(z0, z1) +} + +// ghash calculates the GHASH of data, with the key `key`, and input `dst` +// and `data`, and stores the resulting digest in `dst`. +// +// Note: `dst` is both an input and an output, to support easy implementation +// of GCM. +@(enable_target_feature = "sse2,ssse3,pclmul") +ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check { + if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { + intrinsics.trap() + } + + // Note: BearSSL opts to copy the remainder into a zero-filled + // 64-byte buffer. We do something slightly more simple. + + // Load key and dst (h and y). + yw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(dst))) + h1w := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + yw = byteswap(yw) + h1w = byteswap(h1w) + h1x := bk(h1w) + + // Process 4 blocks at a time + buf := data + l := len(buf) + if l >= GHASH_STRIDE_BYTES_HW { + // Compute h2 = h^2 + h2w, h2x := square_f128(h1w) + + // Compute h3 = h^3 = h*(h^2) + t1 := x86._mm_clmulepi64_si128(h1w, h2w, 0x11) + t3 := x86._mm_clmulepi64_si128(h1w, h2w, 0x00) + t2 := x86._mm_xor_si128( + x86._mm_clmulepi64_si128(h1x, h2x, 0x00), + x86._mm_xor_si128(t1, t3)) + t0 := x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + h3w, h3x := pbk(t0, t1) + + // Compute h4 = h^4 = (h^2)^2 + h4w, h4x := square_f128(h2w) + + for l >= GHASH_STRIDE_BYTES_HW { + aw0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf))) + aw1 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[16:]))) + aw2 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[32:]))) + aw3 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[48:]))) + aw0 = byteswap(aw0) + aw1 = byteswap(aw1) + aw2 = byteswap(aw2) + aw3 = byteswap(aw3) + buf, l = buf[GHASH_STRIDE_BYTES_HW:], l - GHASH_STRIDE_BYTES_HW + + aw0 = x86._mm_xor_si128(aw0, yw) + ax1 := bk(aw1) + ax2 := bk(aw2) + ax3 := bk(aw3) + ax0 := bk(aw0) + + t1 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw0, h4w, 0x11), + x86._mm_clmulepi64_si128(aw1, h3w, 0x11)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw2, h2w, 0x11), + x86._mm_clmulepi64_si128(aw3, h1w, 0x11))) + t3 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw0, h4w, 0x00), + x86._mm_clmulepi64_si128(aw1, h3w, 0x00)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw2, h2w, 0x00), + x86._mm_clmulepi64_si128(aw3, h1w, 0x00))) + t2 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(ax0, h4x, 0x00), + x86._mm_clmulepi64_si128(ax1, h3x, 0x00)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(ax2, h2x, 0x00), + x86._mm_clmulepi64_si128(ax3, h1x, 0x00))) + t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3)) + t0 = x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + yw = x86._mm_unpacklo_epi64(t1, t0) + } + } + + // Process 1 block at a time + src: []byte + for l > 0 { + if l >= _aes.GHASH_BLOCK_SIZE { + src = buf + buf = buf[_aes.GHASH_BLOCK_SIZE:] + l -= _aes.GHASH_BLOCK_SIZE + } else { + tmp: [_aes.GHASH_BLOCK_SIZE]byte + copy(tmp[:], buf) + src = tmp[:] + l = 0 + } + + aw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + aw = byteswap(aw) + + aw = x86._mm_xor_si128(aw, yw) + ax := bk(aw) + + t1 := x86._mm_clmulepi64_si128(aw, h1w, 0x11) + t3 := x86._mm_clmulepi64_si128(aw, h1w, 0x00) + t2 := x86._mm_clmulepi64_si128(ax, h1x, 0x00) + t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3)) + t0 := x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + yw = x86._mm_unpacklo_epi64(t1, t0) + } + + // Write back the hash (dst, aka y) + yw = byteswap(yw) + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), yw) +} diff --git a/core/crypto/_aes/hw_intel/hw_intel_keysched.odin b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin new file mode 100644 index 000000000..911dffbd5 --- /dev/null +++ b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin @@ -0,0 +1,178 @@ +// Copyright (c) 2017 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//+build amd64 +package aes_hw_intel + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:mem" +import "core:simd/x86" + +// Intel AES-NI based implementation. Inspiration taken from BearSSL. +// +// Note: This assumes that the SROA optimization pass is enabled to be +// anything resembling performat otherwise, LLVM will not elide a massive +// number of redundant loads/stores it generates for every intrinsic call. + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step128 :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i { + k1, k2 := k1, k2 + + k2 = x86._mm_shuffle_epi32(k2, 0xff) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + return x86._mm_xor_si128(k1, k2) +} + +@(private = "file", require_results, enable_target_feature = "sse,sse2") +expand_step192a :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + k1, k2, k3 := k1_^, k2_^, k3 + + k3 = x86._mm_shuffle_epi32(k3, 0x55) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, k3) + + tmp := k2 + k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04)) + k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff)) + + k1_, k2_ := k1_, k2_ + k1_^, k2_^ = k1, k2 + + r1 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(tmp), transmute(x86.__m128)(k1), 0x44)) + r2 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(k1), transmute(x86.__m128)(k2), 0x4e)) + + return r1, r2 +} + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step192b :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> x86.__m128i { + k1, k2, k3 := k1_^, k2_^, k3 + + k3 = x86._mm_shuffle_epi32(k3, 0x55) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, k3) + + k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04)) + k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff)) + + k1_, k2_ := k1_, k2_ + k1_^, k2_^ = k1, k2 + + return k1 +} + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step256b :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i { + k1, k2 := k1, k2 + + k2 = x86._mm_shuffle_epi32(k2, 0xaa) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + return x86._mm_xor_si128(k1, k2) +} + +@(private = "file", enable_target_feature = "aes") +derive_dec_keys :: proc(ctx: ^Context, sks: ^[15]x86.__m128i, num_rounds: int) { + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[0]), sks[num_rounds]) + for i in 1 ..< num_rounds { + tmp := x86._mm_aesimc_si128(sks[i]) + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds - i]), tmp) + } + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds]), sks[0]) +} + +@(private, enable_target_feature = "sse,sse2,aes") +keysched :: proc(ctx: ^Context, key: []byte) { + sks: [15]x86.__m128i = --- + + // Compute the encryption keys. + num_rounds, key_len := 0, len(key) + switch key_len { + case _aes.KEY_SIZE_128: + sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + sks[1] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[0], 0x01)) + sks[2] = expand_step128(sks[1], x86._mm_aeskeygenassist_si128(sks[1], 0x02)) + sks[3] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[2], 0x04)) + sks[4] = expand_step128(sks[3], x86._mm_aeskeygenassist_si128(sks[3], 0x08)) + sks[5] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[4], 0x10)) + sks[6] = expand_step128(sks[5], x86._mm_aeskeygenassist_si128(sks[5], 0x20)) + sks[7] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[6], 0x40)) + sks[8] = expand_step128(sks[7], x86._mm_aeskeygenassist_si128(sks[7], 0x80)) + sks[9] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[8], 0x1b)) + sks[10] = expand_step128(sks[9], x86._mm_aeskeygenassist_si128(sks[9], 0x36)) + num_rounds = _aes.ROUNDS_128 + case _aes.KEY_SIZE_192: + k0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + k1 := x86.__m128i{ + intrinsics.unaligned_load((^i64)(raw_data(key[16:]))), + 0, + } + sks[0] = k0 + sks[1], sks[2] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x01)) + sks[3] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x02)) + sks[4], sks[5] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x04)) + sks[6] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x08)) + sks[7], sks[8] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x10)) + sks[9] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x20)) + sks[10], sks[11] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x40)) + sks[12] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x80)) + num_rounds = _aes.ROUNDS_192 + case _aes.KEY_SIZE_256: + sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + sks[1] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key[16:]))) + sks[2] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[1], 0x01)) + sks[3] = expand_step256b(sks[1], x86._mm_aeskeygenassist_si128(sks[2], 0x01)) + sks[4] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[3], 0x02)) + sks[5] = expand_step256b(sks[3], x86._mm_aeskeygenassist_si128(sks[4], 0x02)) + sks[6] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[5], 0x04)) + sks[7] = expand_step256b(sks[5], x86._mm_aeskeygenassist_si128(sks[6], 0x04)) + sks[8] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[7], 0x08)) + sks[9] = expand_step256b(sks[7], x86._mm_aeskeygenassist_si128(sks[8], 0x08)) + sks[10] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[9], 0x10)) + sks[11] = expand_step256b(sks[9], x86._mm_aeskeygenassist_si128(sks[10], 0x10)) + sks[12] = expand_step128(sks[10], x86._mm_aeskeygenassist_si128(sks[11], 0x20)) + sks[13] = expand_step256b(sks[11], x86._mm_aeskeygenassist_si128(sks[12], 0x20)) + sks[14] = expand_step128(sks[12], x86._mm_aeskeygenassist_si128(sks[13], 0x40)) + num_rounds = _aes.ROUNDS_256 + case: + panic("crypto/aes: invalid AES key size") + } + for i in 0 ..= num_rounds { + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_enc[i]), sks[i]) + } + + // Compute the decryption keys. GCM and CTR do not need this, however + // ECB, CBC, OCB3, etc do. + derive_dec_keys(ctx, &sks, num_rounds) + + ctx._num_rounds = num_rounds + + mem.zero_explicit(&sks, size_of(sks)) +} diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin index e895c5fe0..ef305fd21 100644 --- a/core/crypto/aes/aes.odin +++ b/core/crypto/aes/aes.odin @@ -6,7 +6,6 @@ See: - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf */ - package aes import "core:crypto/_aes" diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin index 1821a7bdf..1c5fe31e8 100644 --- a/core/crypto/aes/aes_ctr.odin +++ b/core/crypto/aes/aes_ctr.odin @@ -1,5 +1,6 @@ package aes +import "core:bytes" import "core:crypto/_aes/ct64" import "core:encoding/endian" import "core:math/bits" @@ -37,14 +38,15 @@ init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hard xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { assert(ctx._is_initialized) - // TODO: Enforcing that dst and src alias exactly or not at all - // is a good idea, though odd aliasing should be extremely uncommon. - src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } + if bytes.alias_inexactly(dst, src) { + panic("crypto/aes: dst and src alias inexactly") + } + for remaining := len(src); remaining > 0; { // Process multiple blocks at once if ctx._off == BLOCK_SIZE { @@ -123,8 +125,8 @@ reset_ctr :: proc "contextless" (ctx: ^Context_CTR) { ctx._is_initialized = false } -@(private) -ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { +@(private = "file") +ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check { // Use the optimized hardware implementation if available. if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { ctr_blocks_hw(ctx, dst, src, nr_blocks) @@ -183,17 +185,17 @@ xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]by // performance of this implementation matters to where that // optimization would be worth it, use chacha20poly1305, or a // CPU that isn't e-waste. - if src != nil { - #no_bounds_check { - for i in 0 ..< len(blocks) { - off := i * BLOCK_SIZE - for j in 0 ..< BLOCK_SIZE { - blocks[i][j] ~= src[off + j] + #no_bounds_check { + if src != nil { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + for j in 0 ..< BLOCK_SIZE { + blocks[i][j] ~= src[off + j] + } } - } + } + for i in 0 ..< len(blocks) { + copy(dst[i * BLOCK_SIZE:], blocks[i]) } } - for i in 0 ..< len(blocks) { - copy(dst[i * BLOCK_SIZE:], blocks[i]) - } } diff --git a/core/crypto/aes/aes_ctr_hw_intel.odin b/core/crypto/aes/aes_ctr_hw_intel.odin new file mode 100644 index 000000000..1c9e815ad --- /dev/null +++ b/core/crypto/aes/aes_ctr_hw_intel.odin @@ -0,0 +1,151 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:math/bits" +import "core:mem" +import "core:simd/x86" + +@(private) +CTR_STRIDE_HW :: 4 +@(private) +CTR_STRIDE_BYTES_HW :: CTR_STRIDE_HW * BLOCK_SIZE + +@(private, enable_target_feature = "sse2,aes") +ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check { + hw_ctx := ctx._impl.(Context_Impl_Hardware) + + sks: [15]x86.__m128i = --- + for i in 0 ..= hw_ctx._num_rounds { + sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&hw_ctx._sk_exp_enc[i])) + } + + hw_inc_ctr := #force_inline proc "contextless" (hi, lo: u64) -> (x86.__m128i, u64, u64) { + ret := x86.__m128i{ + i64(intrinsics.byte_swap(hi)), + i64(intrinsics.byte_swap(lo)), + } + + hi, lo := hi, lo + carry: u64 + + lo, carry = bits.add_u64(lo, 1, 0) + hi, _ = bits.add_u64(hi, 0, carry) + return ret, hi, lo + } + + // The latency of AESENC depends on mfg and microarchitecture: + // - 7 -> up to Broadwell + // - 4 -> AMD and Skylake - Cascade Lake + // - 3 -> Ice Lake and newer + // + // This implementation does 4 blocks at once, since performance + // should be "adequate" across most CPUs. + + src, dst := src, dst + nr_blocks := nr_blocks + ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo + + blks: [CTR_STRIDE_HW]x86.__m128i = --- + for nr_blocks >= CTR_STRIDE_HW { + #unroll for i in 0..< CTR_STRIDE_HW { + blks[i], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_xor_si128(blks[i], sks[0]) + } + #unroll for i in 1 ..= 9 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + switch hw_ctx._num_rounds { + case _aes.ROUNDS_128: + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10]) + } + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12]) + } + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14]) + } + } + + xor_blocks_hw(dst, src, blks[:]) + + if src != nil { + src = src[CTR_STRIDE_BYTES_HW:] + } + dst = dst[CTR_STRIDE_BYTES_HW:] + nr_blocks -= CTR_STRIDE_HW + } + + // Handle the remainder. + for nr_blocks > 0 { + blks[0], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo) + + blks[0] = x86._mm_xor_si128(blks[0], sks[0]) + #unroll for i in 1 ..= 9 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + switch hw_ctx._num_rounds { + case _aes.ROUNDS_128: + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10]) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12]) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14]) + } + + xor_blocks_hw(dst, src, blks[:1]) + + if src != nil { + src = src[BLOCK_SIZE:] + } + dst = dst[BLOCK_SIZE:] + nr_blocks -= 1 + } + + // Write back the counter. + ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo + + mem.zero_explicit(&blks, size_of(blks)) + mem.zero_explicit(&sks, size_of(sks)) +} + +@(private, enable_target_feature = "sse2") +xor_blocks_hw :: proc(dst, src: []byte, blocks: []x86.__m128i) { + #no_bounds_check { + if src != nil { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + tmp := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[off:]))) + blocks[i] = x86._mm_xor_si128(blocks[i], tmp) + } + } + for i in 0 ..< len(blocks) { + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst[i * BLOCK_SIZE:])), blocks[i]) + } + } +} diff --git a/core/crypto/aes/aes_ecb_hw_intel.odin b/core/crypto/aes/aes_ecb_hw_intel.odin new file mode 100644 index 000000000..b2ff36a0c --- /dev/null +++ b/core/crypto/aes/aes_ecb_hw_intel.odin @@ -0,0 +1,58 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:simd/x86" + +@(private, enable_target_feature = "sse2,aes") +encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + + blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[0]))) + #unroll for i in 1 ..= 9 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[10]))) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[12]))) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[14]))) + } + + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk) +} + +@(private, enable_target_feature = "sse2,aes") +decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + + blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[0]))) + #unroll for i in 1 ..= 9 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[10]))) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[12]))) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[14]))) + } + + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk) +} diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin index 66ef48db2..25e0cc35b 100644 --- a/core/crypto/aes/aes_gcm.odin +++ b/core/crypto/aes/aes_gcm.odin @@ -1,13 +1,16 @@ package aes +import "core:bytes" import "core:crypto" import "core:crypto/_aes" import "core:crypto/_aes/ct64" import "core:encoding/endian" import "core:mem" -// GCM_NONCE_SIZE is the size of the GCM nonce in bytes. +// GCM_NONCE_SIZE is the default size of the GCM nonce in bytes. GCM_NONCE_SIZE :: 12 +// GCM_NONCE_SIZE_MAX is the maximum size of the GCM nonce in bytes. +GCM_NONCE_SIZE_MAX :: 0x2000000000000000 // floor((2^64 - 1) / 8) bits // GCM_TAG_SIZE is the size of a GCM tag in bytes. GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE @@ -39,6 +42,9 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { if len(dst) != len(plaintext) { panic("crypto/aes: invalid destination ciphertext size") } + if bytes.alias_inexactly(dst, plaintext) { + panic("crypto/aes: dst and plaintext alias inexactly") + } if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext) @@ -47,17 +53,19 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { h: [_aes.GHASH_KEY_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte - init_ghash_ct64(ctx, &h, &j0, nonce) + init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce) // Note: Our GHASH implementation handles appending padding. ct64.ghash(s[:], h[:], aad) - gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true) - final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext)) + gctr_ct64(ctx, dst, &s, plaintext, &h, &j0, true) + final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(plaintext)) copy(tag, s[:]) mem.zero_explicit(&h, len(h)) mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) } // open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext, @@ -73,6 +81,9 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> if len(dst) != len(ciphertext) { panic("crypto/aes: invalid destination plaintext size") } + if bytes.alias_inexactly(dst, ciphertext) { + panic("crypto/aes: dst and ciphertext alias inexactly") + } if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag) @@ -80,12 +91,13 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> h: [_aes.GHASH_KEY_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte - init_ghash_ct64(ctx, &h, &j0, nonce) + init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce) ct64.ghash(s[:], h[:], aad) - gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false) - final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext)) + gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false) + final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(ciphertext)) ok := crypto.compare_constant_time(s[:], tag) == 1 if !ok { @@ -94,6 +106,7 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> mem.zero_explicit(&h, len(h)) mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) mem.zero_explicit(&s, len(s)) return ok @@ -106,19 +119,14 @@ reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { ctx._is_initialized = false } -@(private) +@(private = "file") gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { if len(tag) != GCM_TAG_SIZE { panic("crypto/aes: invalid GCM tag size") } - // The specification supports nonces in the range [1, 2^64) bits - // however per NIST SP 800-38D 5.2.1.1: - // - // > For IVs, it is recommended that implementations restrict support - // > to the length of 96 bits, to promote interoperability, efficiency, - // > and simplicity of design. - if len(nonce) != GCM_NONCE_SIZE { + // The specification supports nonces in the range [1, 2^64) bits. + if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX { panic("crypto/aes: invalid GCM nonce size") } @@ -135,6 +143,7 @@ init_ghash_ct64 :: proc( ctx: ^Context_GCM, h: ^[_aes.GHASH_KEY_SIZE]byte, j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, nonce: []byte, ) { impl := &ctx._impl.(ct64.Context) @@ -142,12 +151,25 @@ init_ghash_ct64 :: proc( // 1. Let H = CIPH(k, 0^128) ct64.encrypt_block(impl, h[:], h[:]) + // Define a block, J0, as follows: + if l := len(nonce); l == GCM_NONCE_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + ct64.ghash(j0[:], h[:], nonce) + + tmp: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) + ct64.ghash(j0[:], h[:], tmp[:]) + } + // ECB encrypt j0, so that we can just XOR with the tag. In theory // this could be processed along with the final GCTR block, to // potentially save a call to AES-ECB, but... just use AES-NI. - copy(j0[:], nonce) - j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 - ct64.encrypt_block(impl, j0[:], j0[:]) + ct64.encrypt_block(impl, j0_enc[:], j0[:]) } @(private = "file") @@ -175,33 +197,27 @@ gctr_ct64 :: proc( s: ^[_aes.GHASH_BLOCK_SIZE]byte, src: []byte, h: ^[_aes.GHASH_KEY_SIZE]byte, - nonce: []byte, + nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, is_seal: bool, -) { +) #no_bounds_check { ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { endian.unchecked_put_u32be(dst[12:], ctr) return ctr + 1 } - // 2. Define a block J_0 as follows: - // if len(IV) = 96, then let J0 = IV || 0^31 || 1 - // - // Note: We only support 96 bit IVs. + // Setup the counter blocks. tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, --- ctrs, blks: [ct64.STRIDE][]byte = ---, --- - ctr: u32 = 2 + ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1 for i in 0 ..< ct64.STRIDE { // Setup scratch space for the keystream. blks[i] = tmp2[i][:] // Pre-copy the IV to all the counter blocks. ctrs[i] = tmp[i][:] - copy(ctrs[i], nonce) + copy(ctrs[i], nonce[:GCM_NONCE_SIZE]) } - // We stitch the GCTR and GHASH operations together, so that only - // one pass over the ciphertext is required. - impl := &ctx._impl.(ct64.Context) src, dst := src, dst diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin new file mode 100644 index 000000000..7d32d4d96 --- /dev/null +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -0,0 +1,243 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto" +import "core:crypto/_aes" +import "core:crypto/_aes/hw_intel" +import "core:encoding/endian" +import "core:mem" +import "core:simd/x86" + +@(private) +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce) + + // Note: Our GHASH implementation handles appending padding. + hw_intel.ghash(s[:], h[:], aad) + gctr_hw(ctx, dst, &s, plaintext, &h, &j0, true) + final_ghash_hw(&s, &h, &j0_enc, len(aad), len(plaintext)) + copy(tag, s[:]) + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) +} + +@(private) +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce) + + hw_intel.ghash(s[:], h[:], aad) + gctr_hw(ctx, dst, &s, ciphertext, &h, &j0, false) + final_ghash_hw(&s, &h, &j0_enc, len(aad), len(ciphertext)) + + ok := crypto.compare_constant_time(s[:], tag) == 1 + if !ok { + mem.zero_explicit(raw_data(dst), len(dst)) + } + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) + mem.zero_explicit(&s, len(s)) + + return ok +} + +@(private = "file") +init_ghash_hw :: proc( + ctx: ^Context_Impl_Hardware, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, + nonce: []byte, +) { + // 1. Let H = CIPH(k, 0^128) + encrypt_block_hw(ctx, h[:], h[:]) + + // Define a block, J0, as follows: + if l := len(nonce); l == GCM_NONCE_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + hw_intel.ghash(j0[:], h[:], nonce) + + tmp: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) + hw_intel.ghash(j0[:], h[:], tmp[:]) + } + + // ECB encrypt j0, so that we can just XOR with the tag. + encrypt_block_hw(ctx, j0_enc[:], j0[:]) +} + +@(private = "file", enable_target_feature = "sse2") +final_ghash_hw :: proc( + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + a_len: int, + t_len: int, +) { + blk: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8) + endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8) + + hw_intel.ghash(s[:], h[:], blk[:]) + j0_vec := intrinsics.unaligned_load((^x86.__m128i)(j0)) + s_vec := intrinsics.unaligned_load((^x86.__m128i)(s)) + s_vec = x86._mm_xor_si128(s_vec, j0_vec) + intrinsics.unaligned_store((^x86.__m128i)(s), s_vec) +} + +@(private = "file", enable_target_feature = "sse2,sse4.1,aes") +gctr_hw :: proc( + ctx: ^Context_Impl_Hardware, + dst: []byte, + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + src: []byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, + is_seal: bool, +) #no_bounds_check { + sks: [15]x86.__m128i = --- + for i in 0 ..= ctx._num_rounds { + sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])) + } + + // Setup the counter block + ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(nonce)) + ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1 + + src, dst := src, dst + + // Note: Instead of doing GHASH and CTR separately, it is more + // performant to interleave (stitch) the two operations together. + // This results in an unreadable mess, so we opt for simplicity + // as performance is adequate. + + blks: [CTR_STRIDE_HW]x86.__m128i = --- + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks >= CTR_STRIDE_HW { + if !is_seal { + hw_intel.ghash(s[:], h[:], src[:CTR_STRIDE_BYTES_HW]) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i], ctr = hw_inc_ctr32(&ctr_blk, ctr) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_xor_si128(blks[i], sks[0]) + } + #unroll for i in 1 ..= 9 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10]) + } + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12]) + } + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14]) + } + } + + xor_blocks_hw(dst, src, blks[:]) + + if is_seal { + hw_intel.ghash(s[:], h[:], dst[:CTR_STRIDE_BYTES_HW]) + } + + src = src[CTR_STRIDE_BYTES_HW:] + dst = dst[CTR_STRIDE_BYTES_HW:] + nr_blocks -= CTR_STRIDE_HW + } + + // Handle the remainder. + for n := len(src); n > 0; { + l := min(n, BLOCK_SIZE) + if !is_seal { + hw_intel.ghash(s[:], h[:], src[:l]) + } + + blks[0], ctr = hw_inc_ctr32(&ctr_blk, ctr) + + blks[0] = x86._mm_xor_si128(blks[0], sks[0]) + #unroll for i in 1 ..= 9 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10]) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12]) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14]) + } + + if l == BLOCK_SIZE { + xor_blocks_hw(dst, src, blks[:1]) + } else { + blk: [BLOCK_SIZE]byte + copy(blk[:], src) + xor_blocks_hw(blk[:], blk[:], blks[:1]) + copy(dst, blk[:l]) + } + if is_seal { + hw_intel.ghash(s[:], h[:], dst[:l]) + } + + dst = dst[l:] + src = src[l:] + n -= l + } + + mem.zero_explicit(&blks, size_of(blks)) + mem.zero_explicit(&sks, size_of(sks)) +} + +// BUG: Sticking this in gctr_hw (like the other implementations) crashes +// the compiler. +// +// src/check_expr.cpp(7892): Assertion Failure: `c->curr_proc_decl->entity` +@(private = "file", enable_target_feature = "sse4.1") +hw_inc_ctr32 :: #force_inline proc "contextless" (src: ^x86.__m128i, ctr: u32) -> (x86.__m128i, u32) { + ret := x86._mm_insert_epi32(src^, i32(intrinsics.byte_swap(ctr)), 3) + return ret, ctr + 1 +} diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin index 94815f61c..5361c6ef0 100644 --- a/core/crypto/aes/aes_impl_hw_gen.odin +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -1,3 +1,4 @@ +//+build !amd64 package aes @(private = "file") diff --git a/core/crypto/aes/aes_impl_hw_intel.odin b/core/crypto/aes/aes_impl_hw_intel.odin new file mode 100644 index 000000000..39ea2dc8d --- /dev/null +++ b/core/crypto/aes/aes_impl_hw_intel.odin @@ -0,0 +1,18 @@ +//+build amd64 +package aes + +import "core:crypto/_aes/hw_intel" + +// is_hardware_accelerated returns true iff hardware accelerated AES +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return hw_intel.is_supported() +} + +@(private) +Context_Impl_Hardware :: hw_intel.Context + +@(private, enable_target_feature = "sse2,aes") +init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) { + hw_intel.init(ctx, key) +} diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index 7f0950d03..73d3e1ea2 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -7,6 +7,7 @@ See: */ package chacha20 +import "core:bytes" import "core:encoding/endian" import "core:math/bits" import "core:mem" @@ -121,14 +122,15 @@ seek :: proc(ctx: ^Context, block_nr: u64) { xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { assert(ctx._is_initialized) - // TODO: Enforcing that dst and src alias exactly or not at all - // is a good idea, though odd aliasing should be extremely uncommon. - src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } + if bytes.alias_inexactly(dst, src) { + panic("crypto/chacha20: dst and src alias inexactly") + } + for remaining := len(src); remaining > 0; { // Process multiple blocks at once if ctx._off == _BLOCK_SIZE { diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin index cd80567e2..323cc45d6 100644 --- a/core/crypto/crypto.odin +++ b/core/crypto/crypto.odin @@ -60,7 +60,11 @@ rand_bytes :: proc (dst: []byte) { _rand_bytes(dst) } - +// random_generator returns a `runtime.Random_Generator` backed by the +// system entropy source. +// +// Support for the system entropy source can be checked with the +// `HAS_RAND_BYTES` boolean constant. random_generator :: proc() -> runtime.Random_Generator { return { procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) { @@ -79,4 +83,4 @@ random_generator :: proc() -> runtime.Random_Generator { }, data = nil, } -} \ No newline at end of file +} diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin index 9cd647cc1..83a976e38 100644 --- a/core/crypto/rand_windows.odin +++ b/core/crypto/rand_windows.odin @@ -8,9 +8,9 @@ HAS_RAND_BYTES :: true @(private) _rand_bytes :: proc(dst: []byte) { - ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)) - if ret != os.ERROR_NONE { - switch ret { + ret := os.Platform_Error(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)) + if ret != nil { + #partial switch ret { case os.ERROR_INVALID_HANDLE: // The handle to the first parameter is invalid. // This should not happen here, since we explicitly pass nil to it diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index 3d41cbe2e..09e16002d 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -16,15 +16,12 @@ Library :: distinct rawptr Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded library available to resolve references in subsequently loaded libraries. -The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`. +The parameter `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`. On `windows` this paramater is ignored. The underlying behaviour is platform specific. On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`. -On `windows` refer to `LoadLibraryW`. - -**Implicit Allocators** -`context.temp_allocator` +On `windows` refer to `LoadLibraryW`. Also temporarily needs an allocator to convert a string. Example: import "core:dynlib" @@ -79,10 +76,7 @@ Loads the address of a procedure/variable from a dynamic library. The underlying behaviour is platform specific. On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`. -On `windows` refer to `GetProcAddress`. - -**Implicit Allocators** -`context.temp_allocator` +On `windows` refer to `GetProcAddress`. Also temporarily needs an allocator to convert a string. Example: import "core:dynlib" @@ -177,9 +171,7 @@ initialize_symbols :: proc( return count, count > 0 } -/* -Returns an error message for the last failed procedure call. -*/ +// Returns an error message for the last failed procedure call. last_error :: proc() -> string { return _last_error() -} +} \ No newline at end of file diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index 866874ee8..bfc724c12 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -16,4 +16,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found _last_error :: proc() -> string { return "" -} +} \ No newline at end of file diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index fb0270ea3..8adaadb2d 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -26,4 +26,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found _last_error :: proc() -> string { err := os.dlerror() return "unknown" if err == "" else err -} +} \ No newline at end of file diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index c7bfe1537..b41abe3b2 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -4,14 +4,12 @@ package dynlib import win32 "core:sys/windows" import "core:strings" -import "base:runtime" import "core:reflect" -_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { +_load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (Library, bool) { // NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wide_path := win32.utf8_to_wstring(path, context.temp_allocator) + wide_path := win32.utf8_to_wstring(path, allocator) + defer free(wide_path, allocator) handle := cast(Library)win32.LoadLibraryW(wide_path) return handle, handle != nil } @@ -21,9 +19,9 @@ _unload_library :: proc(library: Library) -> bool { return bool(ok) } -_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - c_str := strings.clone_to_cstring(symbol, context.temp_allocator) +_symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) { + c_str := strings.clone_to_cstring(symbol, allocator) + defer delete(c_str, allocator) ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str) found = ptr != nil return @@ -33,4 +31,4 @@ _last_error :: proc() -> string { err := win32.System_Error(win32.GetLastError()) err_msg := reflect.enum_string(err) return "unknown" if err_msg == "" else err_msg -} +} \ No newline at end of file diff --git a/core/encoding/cbor/doc.odin b/core/encoding/cbor/doc.odin index 937b1b61b..b3fa36130 100644 --- a/core/encoding/cbor/doc.odin +++ b/core/encoding/cbor/doc.odin @@ -77,8 +77,11 @@ You can look at the default tags provided for pointers on how these implementati Example: package main + import "base:intrinsics" + import "core:encoding/cbor" import "core:fmt" + import "core:reflect" import "core:time" Possibilities :: union { @@ -93,9 +96,32 @@ Example: ignore_this: ^Data `cbor:"-"`, // Ignored by implementation. renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded. my_union: Possibilities, // Union support. + + my_raw: [8]u32 `cbor_tag:"raw"`, // Custom tag that just writes the value as bytes. } main :: proc() { + // Example custom tag implementation that instead of breaking down all parts, + // just writes the value as a big byte blob. This is an advanced feature but very powerful. + RAW_TAG_NR :: 200 + cbor.tag_register_number({ + marshal = proc(_: ^cbor.Tag_Implementation, e: cbor.Encoder, v: any) -> cbor.Marshal_Error { + cbor._encode_u8(e.writer, RAW_TAG_NR, .Tag) or_return + return cbor.err_conv(cbor._encode_bytes(e, reflect.as_bytes(v))) + }, + unmarshal = proc(_: ^cbor.Tag_Implementation, d: cbor.Decoder, _: cbor.Tag_Number, v: any) -> (cbor.Unmarshal_Error) { + hdr := cbor._decode_header(d.reader) or_return + maj, add := cbor._header_split(hdr) + if maj != .Bytes { + return .Bad_Tag_Value + } + + bytes := cbor.err_conv(cbor._decode_bytes(d, add, maj)) or_return + intrinsics.mem_copy_non_overlapping(v.data, raw_data(bytes), len(bytes)) + return nil + }, + }, RAW_TAG_NR, "raw") + now := time.Time{_nsec = 1701117968 * 1e9} data := Data{ @@ -105,21 +131,22 @@ Example: ignore_this = &Data{}, renamed = 123123.125, my_union = 3, + my_raw = {1=1, 2=2, 3=3}, } - + // Marshal the struct into binary CBOR. binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC) - assert(err == nil) + fmt.assertf(err == nil, "marshal error: %v", err) defer delete(binary) - + // Decode the binary data into a `cbor.Value`. decoded, derr := cbor.decode(string(binary)) - assert(derr == nil) + fmt.assertf(derr == nil, "decode error: %v", derr) defer cbor.destroy(decoded) // Turn the CBOR into a human readable representation defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]]. diagnosis, eerr := cbor.to_diagnostic_format(decoded) - assert(eerr == nil) + fmt.assertf(eerr == nil, "to diagnostic error: %v", eerr) defer delete(diagnosis) fmt.println(diagnosis) @@ -127,6 +154,7 @@ Example: Output: { + "my_raw": 200(h'00001000200030000000000000000000'), "my_union": 1010([ "int", 3 diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index d64d22d9c..6657807f5 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a defer if err != nil { strings.builder_destroy(&b) } - if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil { + if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil { return } @@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a // Marshals the given value into a CBOR byte stream written to the given builder. // See docs on the `marshal_into` proc group for more info. -marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error { - return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc) +marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { + return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator) } // Marshals the given value into a CBOR byte stream written to the given writer. // See docs on the `marshal_into` proc group for more info. -marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error { +marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { encoder := Encoder{flags, w, temp_allocator} - return marshal_into_encoder(encoder, v, loc=loc) + return marshal_into_encoder(encoder, v) } // Marshals the given value into a CBOR byte stream written to the given encoder. // See docs on the `marshal_into` proc group for more info. -marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (err: Marshal_Error) { +marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { e := e if e.temp_allocator.procedure == nil { @@ -97,11 +97,14 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e return impl->marshal(e, v) } - ti := runtime.type_info_base(type_info_of(v.id)) - a := any{v.data, ti.id} + ti := runtime.type_info_core(type_info_of(v.id)) + return _marshal_into_encoder(e, v, ti) +} +_marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (err: Marshal_Error) { + a := any{v.data, ti.id} #partial switch info in ti.variant { - case runtime.Type_Info_Named: + case runtime.Type_Info_Named, runtime.Type_Info_Enum, runtime.Type_Info_Bit_Field: unreachable() case runtime.Type_Info_Pointer: @@ -223,18 +226,38 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e } err_conv(_encode_u64(e, u64(info.count), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0.. (e array := (^mem.Raw_Dynamic_Array)(v.data) err_conv(_encode_u64(e, u64(array.len), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0.. (e array := (^mem.Raw_Slice)(v.data) err_conv(_encode_u64(e, u64(array.len), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) for i in 0.. (e builder := strings.builder_from_slice(res[:]) e.writer = strings.to_stream(&builder) - assert(_encode_u64(e, u64(len(str)), .Text) == nil) + err := _encode_u64(e, u64(len(str)), .Text) + assert(err == nil) res[9] = u8(len(builder.buf)) assert(res[9] < 10) return @@ -463,7 +507,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e } n: u64; { - for _, i in info.names { + for _, i in info.names[:info.field_count] { if field_name(info, i) != "-" { n += 1 } @@ -479,7 +523,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return defer delete(entries) - for _, i in info.names { + for _, i in info.names[:info.field_count] { fname := field_name(info, i) if fname == "-" { continue @@ -497,7 +541,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e marshal_entry(e, info, v, entry.name, entry.field) or_return } } else { - for _, i in info.names { + for _, i in info.names[:info.field_count] { fname := field_name(info, i) if fname == "-" { continue @@ -542,9 +586,6 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e return marshal_into(e, any{v.data, vti.id}) - case runtime.Type_Info_Enum: - return marshal_into(e, any{v.data, info.base.id}) - case runtime.Type_Info_Bit_Set: // Store bit_set as big endian just like the protocol. do_byte_swap := !reflect.bit_set_is_big_endian(v) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 289895e98..c54660839 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -96,7 +96,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.a ti = reflect.type_info_base(variant) if !reflect.is_pointer_internally(variant) { tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} - assert(_assign_int(tag, 1)) + assigned := _assign_int(tag, 1) + assert(assigned) } } } @@ -520,9 +521,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Array: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, t.count) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > t.count { return _unsupported(v, hdr) } @@ -534,9 +533,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Enumerated_Array: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, t.count) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > t.count { return _unsupported(v, hdr) } @@ -548,9 +545,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Complex: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, 2) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > 2 { return _unsupported(v, hdr) } @@ -570,9 +565,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Quaternion: - _, scap := err_conv(_decode_len_container(d, add)) or_return - length := min(scap, 4) - + length, _ := err_conv(_decode_len_container(d, add)) or_return if length > 4 { return _unsupported(v, hdr) } @@ -626,14 +619,14 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, #partial switch t in ti.variant { case reflect.Type_Info_Struct: - if t.is_raw_union { + if .raw_union in t.flags { return _unsupported(v, hdr) } length, _ := err_conv(_decode_len_container(d, add)) or_return unknown := length == -1 fields := reflect.struct_fields_zipped(ti.id) - + for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 { // Decode key, keys can only be strings. key: string @@ -646,7 +639,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, key = keyv } defer delete(key, context.temp_allocator) - + // Find matching field. use_field_idx := -1 { diff --git a/core/encoding/csv/example.odin b/core/encoding/csv/example.odin index 24722589d..d791eb33b 100644 --- a/core/encoding/csv/example.odin +++ b/core/encoding/csv/example.odin @@ -38,9 +38,9 @@ iterate_csv_from_stream :: proc(filename: string) { r.reuse_record_buffer = true // Without it you have to each of the fields within it defer csv.reader_destroy(&r) - handle, errno := os.open(filename) - if errno != os.ERROR_NONE { - fmt.printfln("Error opening file: %v", filename) + handle, err := os.open(filename) + if err != nil { + fmt.eprintfln("Error opening file: %v", filename) return } defer os.close(handle) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index eb0ad9e7c..2bb7996a3 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -82,15 +82,17 @@ Map :: distinct map[string]map[string]string load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) { unquote :: proc(val: string) -> (string, runtime.Allocator_Error) { - v, allocated, ok := strconv.unquote_string(val) - if !ok { - return strings.clone(val) + if len(val) > 0 && (val[0] == '"' || val[0] == '\'') { + v, allocated, ok := strconv.unquote_string(val) + if !ok { + return strings.clone(val) + } + if allocated { + return v, nil + } + return strings.clone(v), nil } - if allocated { - return v, nil - } - return strings.clone(v) - + return strings.clone(val) } context.allocator = allocator @@ -121,7 +123,7 @@ load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options : data := os.read_entire_file(path, allocator) or_return defer delete(data, allocator) m, err = load_map_from_string(string(data), allocator, options) - ok = err != nil + ok = err == nil defer if !ok { delete_map(m) } @@ -142,6 +144,7 @@ delete_map :: proc(m: Map) { delete(value, allocator) } delete(section) + delete(pairs) } delete(m) } diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 0464c24d1..009bf7ade 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -100,38 +100,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Integer: buf: [40]byte - u: u128 - switch i in a { - case i8: u = u128(i) - case i16: u = u128(i) - case i32: u = u128(i) - case i64: u = u128(i) - case i128: u = u128(i) - case int: u = u128(i) - case u8: u = u128(i) - case u16: u = u128(i) - case u32: u = u128(i) - case u64: u = u128(i) - case u128: u = u128(i) - case uint: u = u128(i) - case uintptr: u = u128(i) - - case i16le: u = u128(i) - case i32le: u = u128(i) - case i64le: u = u128(i) - case u16le: u = u128(i) - case u32le: u = u128(i) - case u64le: u = u128(i) - case u128le: u = u128(i) - - case i16be: u = u128(i) - case i32be: u = u128(i) - case i64be: u = u128(i) - case u16be: u = u128(i) - case u32be: u = u128(i) - case u64be: u = u128(i) - case u128be: u = u128(i) - } + u := cast_any_int_to_u128(a) s: string @@ -310,7 +279,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case cstring: name = string(s) } opt_write_key(w, opt, name) or_return - + case runtime.Type_Info_Integer: + buf: [40]byte + u := cast_any_int_to_u128(ka) + name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) + + opt_write_key(w, opt, name) or_return case: return .Unsupported_Type } } @@ -406,10 +380,15 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: ti := runtime.type_info_base(type_info_of(v.id)) info := ti.variant.(runtime.Type_Info_Struct) first_iteration := true - for name, i in info.names { + for name, i in info.names[:info.field_count] { omitempty := false json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")) + + if json_name == "-" { + continue + } + for flag in strings.split_iterator(&extra, ",") { switch flag { case "omitempty": @@ -657,3 +636,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E return } + +@(private) +cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 { + u: u128 = 0 + switch i in any_int_value { + case i8: u = u128(i) + case i16: u = u128(i) + case i32: u = u128(i) + case i64: u = u128(i) + case i128: u = u128(i) + case int: u = u128(i) + case u8: u = u128(i) + case u16: u = u128(i) + case u32: u = u128(i) + case u64: u = u128(i) + case u128: u = u128(i) + case uint: u = u128(i) + case uintptr: u = u128(i) + + case i16le: u = u128(i) + case i32le: u = u128(i) + case i64le: u = u128(i) + case u16le: u = u128(i) + case u32le: u = u128(i) + case u64le: u = u128(i) + case u128le: u = u128(i) + + case i16be: u = u128(i) + case i32be: u = u128(i) + case i64be: u = u128(i) + case u16be: u = u128(i) + case u32be: u = u128(i) + case u64be: u = u128(i) + case u128be: u = u128(i) + } + + return u +} \ No newline at end of file diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index eb59e7838..127bce650 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -363,12 +363,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } v := v - v = reflect.any_base(v) - ti := type_info_of(v.id) + ti := reflect.type_info_base(type_info_of(v.id)) #partial switch t in ti.variant { case reflect.Type_Info_Struct: - if t.is_raw_union { + if .raw_union in t.flags { return UNSUPPORTED_TYPE } @@ -475,7 +474,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } case reflect.Type_Info_Map: - if !reflect.is_string(t.key) { + if !reflect.is_string(t.key) && !reflect.is_integer(t.key) { return UNSUPPORTED_TYPE } raw_map := (^mem.Raw_Map)(v.data) @@ -492,25 +491,39 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm key, _ := parse_object_key(p, p.allocator) unmarshal_expect_token(p, .Colon) - + mem.zero_slice(elem_backing) if uerr := unmarshal_value(p, map_backing_value); uerr != nil { delete(key, p.allocator) return uerr } - key_ptr := rawptr(&key) + key_ptr: rawptr - key_cstr: cstring - if reflect.is_cstring(t.key) { - key_cstr = cstring(raw_data(key)) - key_ptr = &key_cstr + #partial switch tk in t.key.variant { + case runtime.Type_Info_String: + key_ptr = rawptr(&key) + key_cstr: cstring + if reflect.is_cstring(t.key) { + key_cstr = cstring(raw_data(key)) + key_ptr = &key_cstr + } + case runtime.Type_Info_Integer: + i, ok := strconv.parse_i128(key) + if !ok { return UNSUPPORTED_TYPE } + key_ptr = rawptr(&i) + case: return UNSUPPORTED_TYPE } - + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) if set_ptr == nil { delete(key, p.allocator) } + + // there's no need to keep string value on the heap, since it was copied into map + if reflect.is_integer(t.key) { + delete(key, p.allocator) + } if parse_comma(p) { break map_loop diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index 2d06038b7..a2bbaf28e 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -126,7 +126,7 @@ error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { t.error_count += 1 } -@(optimization_mode="speed") +@(optimization_mode="favor_size") advance_rune :: proc(t: ^Tokenizer) { #no_bounds_check { /* @@ -170,7 +170,7 @@ peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte { return 0 } -@(optimization_mode="speed") +@(optimization_mode="favor_size") skip_whitespace :: proc(t: ^Tokenizer) { for { switch t.ch { @@ -182,7 +182,7 @@ skip_whitespace :: proc(t: ^Tokenizer) { } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") is_letter :: proc(r: rune) -> bool { if r < utf8.RUNE_SELF { switch r { @@ -296,7 +296,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { return } -@(optimization_mode="speed") +@(optimization_mode="favor_size") scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close := false, multiline := true) -> (value: string, err: Error) { err = .None @@ -414,4 +414,4 @@ scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token { lit = string(t.src[offset : t.offset]) } return Token{kind, lit, pos} -} \ No newline at end of file +} diff --git a/core/flags/errors.odin b/core/flags/errors.odin index 21ea05477..6e48f6ccf 100644 --- a/core/flags/errors.odin +++ b/core/flags/errors.odin @@ -28,7 +28,7 @@ Parse_Error :: struct { // Provides more granular information than what just a string could hold. Open_File_Error :: struct { filename: string, - errno: os.Errno, + errno: os.Error, mode: int, perms: int, } diff --git a/core/flags/internal_assignment.odin b/core/flags/internal_assignment.odin index 4c4ee58fe..bb49977eb 100644 --- a/core/flags/internal_assignment.odin +++ b/core/flags/internal_assignment.odin @@ -12,7 +12,7 @@ import "core:reflect" // Push a positional argument onto a data struct, checking for specified // positionals first before adding it to a fallback field. -@(optimization_mode="size") +@(optimization_mode="favor_size") push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: string) -> (error: Error) { if bit_array.get(&parser.filled_pos, parser.filled_pos.max_index) { // The max index is set, which means we're out of space. @@ -74,7 +74,7 @@ register_field :: proc(parser: ^Parser, field: reflect.Struct_Field, index: int) } // Set a `-flag` argument, Odin-style. -@(optimization_mode="size") +@(optimization_mode="favor_size") set_odin_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (error: Error) { // We make a special case for help requests. switch name { @@ -100,7 +100,7 @@ set_odin_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (error: Erro } // Set a `-flag` argument, UNIX-style. -@(optimization_mode="size") +@(optimization_mode="favor_size") set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args: int, error: Error) { // We make a special case for help requests. switch name { @@ -137,7 +137,7 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args } // Set a `-flag:option` argument. -@(optimization_mode="size") +@(optimization_mode="favor_size") set_option :: proc(model: ^$T, parser: ^Parser, name, option: string) -> (error: Error) { field, index := get_field_by_name(model, name) or_return @@ -176,7 +176,7 @@ set_option :: proc(model: ^$T, parser: ^Parser, name, option: string) -> (error: } // Set a `-map:key=value` argument. -@(optimization_mode="size") +@(optimization_mode="favor_size") set_key_value :: proc(model: ^$T, parser: ^Parser, name, key, value: string) -> (error: Error) { field, index := get_field_by_name(model, name) or_return diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index b7aecdef3..4c1db5d0b 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -13,7 +13,7 @@ import "core:strings" @require import "core:time/datetime" import "core:unicode/utf8" -@(optimization_mode="size") +@(optimization_mode="favor_size") parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool { bounded_int :: proc(value, min, max: i128) -> (result: i128, ok: bool) { return value, min <= value && value <= max @@ -202,7 +202,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: // especially with files. // // We want to provide as informative as an error as we can. -@(optimization_mode="size", disabled=NO_CORE_NAMED_TYPES) +@(optimization_mode="favor_size", disabled=NO_CORE_NAMED_TYPES) parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: typeid, arg_tag: string, out_error: ^Error) { // Core types currently supported: // @@ -254,8 +254,8 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: } handle, errno := os.open(str, mode, perms) - if errno != 0 { - // NOTE(Feoramund): os.Errno is system-dependent, and there's + if errno != nil { + // NOTE(Feoramund): os.Error is system-dependent, and there's // currently no good way to translate them all into strings. // // The upcoming `os2` package will hopefully solve this. @@ -320,7 +320,7 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: } } -@(optimization_mode="size") +@(optimization_mode="favor_size") set_unbounded_integer_by_type :: proc(ptr: rawptr, value: $T, data_type: typeid) where intrinsics.type_is_integer(T) { switch data_type { case i8: (^i8) (ptr)^ = cast(i8) value @@ -367,7 +367,7 @@ set_unbounded_integer_by_type :: proc(ptr: rawptr, value: $T, data_type: typeid) } } -@(optimization_mode="size") +@(optimization_mode="favor_size") parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info, arg_tag: string) -> (error: Error) { #partial switch specific_type_info in type_info.variant { case runtime.Type_Info_Named: diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin index fd4bafeef..b71cf9fe7 100644 --- a/core/flags/internal_validation.odin +++ b/core/flags/internal_validation.odin @@ -11,7 +11,7 @@ package flags @require import "core:strings" // This proc is used to assert that `T` meets the expectations of the library. -@(optimization_mode="size", disabled=ODIN_DISABLE_ASSERT) +@(optimization_mode="favor_size", disabled=ODIN_DISABLE_ASSERT) validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_location) { positionals_assigned_so_far: bit_array.Bit_Array defer bit_array.destroy(&positionals_assigned_so_far) @@ -162,7 +162,7 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_ // Validate that all the required arguments are set and that the set arguments // are up to the program's expectations. -@(optimization_mode="size") +@(optimization_mode="favor_size") validate_arguments :: proc(model: ^$T, parser: ^Parser) -> Error { check_fields: for field, index in reflect.struct_fields_zipped(T) { was_set := bit_array.get(&parser.fields_set, index) diff --git a/core/flags/parsing.odin b/core/flags/parsing.odin index b6b63fdb6..2d8ce34eb 100644 --- a/core/flags/parsing.odin +++ b/core/flags/parsing.odin @@ -32,7 +32,7 @@ Inputs: Returns: - error: A union of errors; parsing, file open, a help request, or validation. */ -@(optimization_mode="size") +@(optimization_mode="favor_size") parse :: proc( model: ^$T, args: []string, diff --git a/core/flags/usage.odin b/core/flags/usage.odin index 48137b6cd..c42df7576 100644 --- a/core/flags/usage.odin +++ b/core/flags/usage.odin @@ -17,7 +17,7 @@ Inputs: - program: The name of the program, usually the first argument to `os.args`. - style: The argument parsing style, required to show flags in the proper style. */ -@(optimization_mode="size") +@(optimization_mode="favor_size") write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", style: Parsing_Style = .Odin) { // All flags get their tags parsed so they can be reasoned about later. Flag :: struct { diff --git a/core/flags/util.odin b/core/flags/util.odin index e4f32eea1..f1bd60e75 100644 --- a/core/flags/util.odin +++ b/core/flags/util.odin @@ -19,7 +19,7 @@ Inputs: - allocator: (default: context.allocator) - loc: The caller location for debugging purposes (default: #caller_location) */ -@(optimization_mode="size") +@(optimization_mode="favor_size") parse_or_exit :: proc( model: ^$T, program_args: []string, @@ -63,7 +63,7 @@ Inputs: - error: The error returned from `parse`. - style: The argument parsing style, required to show flags in the proper style, when usage is shown. */ -@(optimization_mode="size") +@(optimization_mode="favor_size") print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) { stderr := os.stream_from_handle(os.stderr) stdout := os.stream_from_handle(os.stdout) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index bd5f2b155..7b86fd1b7 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -334,6 +334,27 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { message := tprintf(fmt, ..args) p("Panic", message, loc) } + +// Creates a formatted C string +// +// *Allocates Using Context's Allocator* +// +// Inputs: +// - args: A variadic list of arguments to be formatted. +// - sep: An optional separator string (default is a single space). +// +// Returns: A formatted C string. +// +@(require_results) +caprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> cstring { + str: strings.Builder + strings.builder_init(&str, allocator) + sbprint(&str, ..args, sep=sep) + strings.write_byte(&str, 0) + s := strings.to_string(str) + return cstring(raw_data(s)) +} + // Creates a formatted C string // // *Allocates Using Context's Allocator* @@ -346,9 +367,9 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { // Returns: A formatted C string // @(require_results) -caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { +caprintf :: proc(format: string, args: ..any, allocator := context.allocator, newline := false) -> cstring { str: strings.Builder - strings.builder_init(&str) + strings.builder_init(&str, allocator) sbprintf(&str, format, ..args, newline=newline) strings.write_byte(&str, 0) s := strings.to_string(str) @@ -365,8 +386,8 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // Returns: A formatted C string // @(require_results) -caprintfln :: proc(format: string, args: ..any) -> cstring { - return caprintf(format, ..args, newline=true) +caprintfln :: proc(format: string, args: ..any, allocator := context.allocator) -> cstring { + return caprintf(format, ..args, allocator=allocator, newline=true) } // Creates a formatted C string // @@ -380,12 +401,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring { // @(require_results) ctprint :: proc(args: ..any, sep := " ") -> cstring { - str: strings.Builder - strings.builder_init(&str, context.temp_allocator) - sbprint(&str, ..args, sep=sep) - strings.write_byte(&str, 0) - s := strings.to_string(str) - return cstring(raw_data(s)) + return caprint(args=args, sep=sep, allocator=context.temp_allocator) } // Creates a formatted C string // @@ -400,12 +416,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring { // @(require_results) ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { - str: strings.Builder - strings.builder_init(&str, context.temp_allocator) - sbprintf(&str, format, ..args, newline=newline) - strings.write_byte(&str, 0) - s := strings.to_string(str) - return cstring(raw_data(s)) + return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=newline) } // Creates a formatted C string, followed by a newline. // @@ -419,7 +430,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // @(require_results) ctprintfln :: proc(format: string, args: ..any) -> cstring { - return ctprintf(format, ..args, newline=true) + return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=true) } // Formats using the default print settings and writes to the given strings.Builder // @@ -951,10 +962,10 @@ fmt_bad_verb :: proc(fi: ^Info, verb: rune) { io.write_string(fi.writer, "%!", &fi.n) io.write_rune(fi.writer, verb, &fi.n) io.write_byte(fi.writer, '(', &fi.n) - if fi.arg.id != nil { - reflect.write_typeid(fi.writer, fi.arg.id, &fi.n) + if arg := fi.arg; arg != nil { + reflect.write_typeid(fi.writer, arg.id, &fi.n) io.write_byte(fi.writer, '=', &fi.n) - fmt_value(fi, fi.arg, 'v') + fmt_value(fi, arg, 'v') } else { io.write_string(fi.writer, "", &fi.n) } @@ -1861,7 +1872,7 @@ handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Str if optional_len == nil { return } - for f, i in info.names { + for f, i in info.names[:info.field_count] { if f != field_name { continue } @@ -1965,7 +1976,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St fmt_bad_verb(fi, the_verb) return } - if info.is_raw_union { + if .raw_union in info.flags { if type_name == "" { io.write_string(fi.writer, "(raw union)", &fi.n) } else { @@ -1989,7 +2000,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St // fi.hash = false; fi.indent += 1 - is_empty := len(info.names) == 0 + is_empty := info.field_count == 0 if !is_soa && hash && !is_empty { io.write_byte(fi.writer, '\n', &fi.n) @@ -2010,17 +2021,17 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St base_type_name = v.name } - actual_field_count := len(info.names) + actual_field_count := info.field_count n := uintptr(info.soa_len) if info.soa_kind == .Slice { - actual_field_count = len(info.names)-1 // len + actual_field_count = info.field_count-1 // len n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^) } else if info.soa_kind == .Dynamic { - actual_field_count = len(info.names)-3 // len, cap, allocator + actual_field_count = info.field_count-3 // len, cap, allocator n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^) } @@ -2099,7 +2110,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St } } else { field_count := -1 - for name, i in info.names { + for name, i in info.names[:info.field_count] { optional_len: int = -1 use_nul_termination: bool = false verb := the_verb if the_verb == 'w' else 'v' @@ -2605,7 +2616,7 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit field_count := -1 - for name, i in info.names { + for name, i in info.names[:info.field_count] { field_verb := verb if handle_bit_field_tag(v.data, info, i, &field_verb) { continue @@ -2751,9 +2762,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { elem := runtime.type_info_base(info.elem) if elem != nil { if n, ok := fi.optional_len.?; ok { + fi.optional_len = nil fmt_array(fi, ptr, n, elem.size, elem, verb) return } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb) return } @@ -2855,8 +2868,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := info.count ptr := v.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } @@ -2867,8 +2882,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := slice.len ptr := slice.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } @@ -2879,8 +2896,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := array.len ptr := array.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } diff --git a/core/hash/crc.odin b/core/hash/crc.odin index cb3e36881..68b8f5369 100644 --- a/core/hash/crc.odin +++ b/core/hash/crc.odin @@ -1,6 +1,6 @@ package hash -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check { result = seed #no_bounds_check for b in data { @@ -14,7 +14,7 @@ crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result: bit-reversed, with one's complement pre and post processing. Based on Mark Adler's v1.4 implementation in C under the ZLIB license. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { data := data result := ~u64le(seed) @@ -52,7 +52,7 @@ crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_ /* Generator polynomial: x^64 + x^4 + x^3 + x + 1 */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc64_iso_3306 :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { result := seed @@ -738,4 +738,4 @@ crc64_iso_3306_inverse :: proc "contextless" (data: []byte, seed := u64(0)) -> u 0x9fc0, 0x9e70, 0x9ca0, 0x9d10, 0x9480, 0x9530, 0x97e0, 0x9650, 0x9240, 0x93f0, 0x9120, 0x9090, -} \ No newline at end of file +} diff --git a/core/hash/crc32.odin b/core/hash/crc32.odin index 5dde467a7..a7f68207e 100644 --- a/core/hash/crc32.odin +++ b/core/hash/crc32.odin @@ -2,7 +2,7 @@ package hash import "base:intrinsics" -@(optimization_mode="speed") +@(optimization_mode="favor_size") crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 #no_bounds_check { crc := ~seed buffer := raw_data(data) diff --git a/core/hash/hash.odin b/core/hash/hash.odin index fb170bfe4..45f524d8a 100644 --- a/core/hash/hash.odin +++ b/core/hash/hash.odin @@ -3,7 +3,7 @@ package hash import "core:mem" import "base:intrinsics" -@(optimization_mode="speed") +@(optimization_mode="favor_size") adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_check { ADLER_CONST :: 65521 @@ -46,7 +46,7 @@ adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_c return (u32(b) << 16) | u32(a) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") djb2 :: proc "contextless" (data: []byte, seed := u32(5381)) -> u32 { hash: u32 = seed for b in data { @@ -73,7 +73,7 @@ djbx33a :: proc "contextless" (data: []byte, seed := u32(5381)) -> (result: [16] } // If you have a choice, prefer fnv32a -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv32_no_a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { @@ -86,7 +86,7 @@ fnv32 :: fnv32_no_a // NOTE(bill): Not a fan of these aliases but seems necessar fnv64 :: fnv64_no_a // NOTE(bill): Not a fan of these aliases but seems necessary // If you have a choice, prefer fnv64a -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { @@ -94,7 +94,7 @@ fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) } return h } -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { @@ -103,7 +103,7 @@ fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { return h } -@(optimization_mode="speed") +@(optimization_mode="favor_size") fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { @@ -112,7 +112,7 @@ fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> return h } -@(optimization_mode="speed") +@(optimization_mode="favor_size") jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { @@ -126,7 +126,7 @@ jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { return hash } -@(optimization_mode="speed") +@(optimization_mode="favor_size") murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { c1_32: u32 : 0xcc9e2d51 c2_32: u32 : 0x1b873593 @@ -177,7 +177,7 @@ murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { } // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96 -@(optimization_mode="speed") +@(optimization_mode="favor_size") murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0xc6a4a7935bd1e995 r :: 47 @@ -218,7 +218,7 @@ murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { } // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140 -@(optimization_mode="speed") +@(optimization_mode="favor_size") murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0x5bd1e995 r :: 24 @@ -286,7 +286,7 @@ murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { return u64(h1)<<32 | u64(h2) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") sdbm :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { diff --git a/core/hash/xxhash/common.odin b/core/hash/xxhash/common.odin index faf88e0d4..bbeb60db3 100644 --- a/core/hash/xxhash/common.odin +++ b/core/hash/xxhash/common.odin @@ -67,17 +67,17 @@ when !XXH_DISABLE_PREFETCH { } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_rotl32 :: #force_inline proc(x, r: u32) -> (res: u32) { return ((x << r) | (x >> (32 - r))) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_rotl64 :: #force_inline proc(x, r: u64) -> (res: u64) { return ((x << r) | (x >> (64 - r))) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_read32 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) -> (res: u32) { if XXH_FORCE_MEMORY_ACCESS == 2 || alignment == .Aligned { #no_bounds_check b := (^u32le)(&buf[0])^ @@ -89,7 +89,7 @@ XXH32_read32 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_read64 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) -> (res: u64) { if XXH_FORCE_MEMORY_ACCESS == 2 || alignment == .Aligned { #no_bounds_check b := (^u64le)(&buf[0])^ @@ -99,4 +99,4 @@ XXH64_read64 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) mem_copy(&b, raw_data(buf[:]), 8) return u64(b) } -} \ No newline at end of file +} diff --git a/core/hash/xxhash/xxhash_3.odin b/core/hash/xxhash/xxhash_3.odin index be2531b6e..9e159260b 100644 --- a/core/hash/xxhash/xxhash_3.odin +++ b/core/hash/xxhash/xxhash_3.odin @@ -111,13 +111,13 @@ XXH128_canonical :: struct { @param lhs, rhs The 64-bit integers to multiply @return The low 64 bits of the product XOR'd by the high 64 bits. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_mul_64_to_128_fold_64 :: #force_inline proc(lhs, rhs: xxh_u64) -> (res: xxh_u64) { t := u128(lhs) * u128(rhs) return u64(t & 0xFFFFFFFFFFFFFFFF) ~ u64(t >> 64) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, #any_int shift: uint) -> (res: xxh_u64) { return v ~ (v >> shift) } @@ -125,7 +125,7 @@ XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, #any_int shift: uint) -> (res: /* This is a fast avalanche stage, suitable when input bits are already partially mixed */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_avalanche :: #force_inline proc(h64: xxh_u64) -> (res: xxh_u64) { res = XXH_xorshift_64(h64, 37) res *= 0x165667919E3779F9 @@ -137,7 +137,7 @@ XXH3_avalanche :: #force_inline proc(h64: xxh_u64) -> (res: xxh_u64) { This is a stronger avalanche, inspired by Pelle Evensen's rrmxmx preferable when input has not been previously mixed */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_rrmxmx :: #force_inline proc(h64, length: xxh_u64) -> (res: xxh_u64) { /* this mix is inspired by Pelle Evensen's rrmxmx */ res = h64 @@ -166,7 +166,7 @@ XXH3_rrmxmx :: #force_inline proc(h64, length: xxh_u64) -> (res: xxh_u64) { fast for a _128-bit_ hash on 32-bit (it usually clears XXH64). */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_1to3_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { /* A doubled version of 1to3_64b with different constants. */ length := len(input) @@ -190,7 +190,7 @@ XXH3_len_1to3_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_4to8_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) seed := seed @@ -219,7 +219,7 @@ XXH3_len_4to8_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_9to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -261,7 +261,7 @@ XXH3_len_9to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u /* Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_0to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -279,7 +279,7 @@ XXH3_len_0to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u /* A bit slower than XXH3_mix16B, but handles multiply by zero better. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH128_mix32B :: #force_inline proc(acc: xxh_u128, input_1: []u8, input_2: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { acc128 := XXH128_hash_t{ h = acc, @@ -293,7 +293,7 @@ XXH128_mix32B :: #force_inline proc(acc: xxh_u128, input_1: []u8, input_2: []u8, } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_17to128_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -323,7 +323,7 @@ XXH3_len_17to128_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh unreachable() } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_129to240_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) { length := len(input) @@ -379,7 +379,7 @@ XXH3_INIT_ACC :: [XXH_ACC_NB]xxh_u64{ XXH_SECRET_MERGEACCS_START :: 11 -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_internal :: #force_inline proc( input: []u8, secret: []u8, @@ -407,7 +407,7 @@ XXH3_hashLong_128b_internal :: #force_inline proc( /* * It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -415,12 +415,12 @@ XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64, /* * It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_withSecret :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_128b_withSeed_internal :: #force_inline proc( input: []u8, seed: xxh_u64, secret: []u8, f_acc512: XXH3_accumulate_512_f, @@ -441,14 +441,14 @@ XXH3_hashLong_128b_withSeed_internal :: #force_inline proc( /* * It's important for performance that XXH3_hashLong is not inlined. */ - @(optimization_mode="speed") + @(optimization_mode="favor_size") XXH3_hashLong_128b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_withSeed_internal(input, seed, secret, XXH3_accumulate_512, XXH3_scramble_accumulator , XXH3_init_custom_secret) } XXH3_hashLong128_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128bits_internal :: #force_inline proc( input: []u8, seed: xxh_u64, secret: []u8, f_hl128: XXH3_hashLong128_f) -> (res: XXH3_128_hash) { @@ -474,17 +474,17 @@ XXH3_128bits_internal :: #force_inline proc( } /* === Public XXH128 API === */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128_default :: proc(input: []u8) -> (hash: XXH3_128_hash) { return XXH3_128bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_128b_withSeed) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: XXH3_128_hash) { return XXH3_128bits_internal(input, seed, XXH3_kSecret[:], XXH3_hashLong_128b_withSeed) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_128_with_secret :: proc(input: []u8, secret: []u8) -> (hash: XXH3_128_hash) { return XXH3_128bits_internal(input, 0, secret, XXH3_hashLong_128b_withSecret) } @@ -519,7 +519,7 @@ XXH3_128 :: proc { XXH3_128_default, XXH3_128_with_seed, XXH3_128_with_secret } The XOR mixing hides individual parts of the secret and increases entropy. This adds an extra layer of strength for custom secrets. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_1to3_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u32(len(input)) assert(input != nil) @@ -542,7 +542,7 @@ XXH3_len_1to3_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_4to8_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u32(len(input)) assert(input != nil) @@ -562,7 +562,7 @@ XXH3_len_4to8_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_9to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u64(len(input)) assert(input != nil) @@ -579,7 +579,7 @@ XXH3_len_9to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_0to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { length := u64(len(input)) assert(input != nil) @@ -621,7 +621,7 @@ XXH3_len_0to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6 by this, although it is always a good idea to use a proper seed if you care about strength. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_mix16B :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { input_lo := XXH64_read64(input[0:]) input_hi := XXH64_read64(input[8:]) @@ -632,7 +632,7 @@ XXH3_mix16B :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> ( } /* For mid range keys, XXH3 uses a Mum-hash variant. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_17to128_64b :: proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { assert(len(secret) >= XXH3_SECRET_SIZE_MIN) length := len(input) @@ -665,7 +665,7 @@ XXH3_MIDSIZE_MAX :: 240 XXH3_MIDSIZE_STARTOFFSET :: 3 XXH3_MIDSIZE_LASTOFFSET :: 17 -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_len_129to240_64b :: proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) { assert(len(secret) >= XXH3_SECRET_SIZE_MIN) length := len(input) @@ -699,7 +699,7 @@ XXH_SECRET_CONSUME_RATE :: 8 /* nb of secret bytes consumed at each accumulatio XXH_ACC_NB :: (XXH_STRIPE_LEN / size_of(xxh_u64)) XXH_SECRET_LASTACC_START :: 7 /* not aligned on 8, last secret is different from acc & scrambler */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH_writeLE64 :: #force_inline proc(dst: []u8, v64: u64le) { v := v64 mem_copy(raw_data(dst), &v, size_of(v64)) @@ -737,7 +737,7 @@ XXH3_scramble_accumulator : XXH3_scramble_accumulator_f = XXH3_scramble_accumula XXH3_init_custom_secret : XXH3_init_custom_secret_f = XXH3_init_custom_secret_scalar /* scalar variants - universal */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8) { xacc := acc /* presumed aligned */ xinput := input /* no alignment restriction */ @@ -754,7 +754,7 @@ XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, se } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_scramble_accumulator_scalar :: #force_inline proc(acc: []xxh_u64, secret: []u8) { xacc := acc /* presumed aligned */ xsecret := secret /* no alignment restriction */ @@ -771,7 +771,7 @@ XXH3_scramble_accumulator_scalar :: #force_inline proc(acc: []xxh_u64, secret: [ } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_init_custom_secret_scalar :: #force_inline proc(custom_secret: []u8, seed64: xxh_u64) { #assert((XXH_SECRET_DEFAULT_SIZE & 15) == 0) @@ -791,7 +791,7 @@ XXH_PREFETCH_DIST :: 320 * Loops over XXH3_accumulate_512(). * Assumption: nbStripes will not overflow the secret size */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_accumulate :: #force_inline proc( acc: []xxh_u64, input: []u8, secret: []u8, nbStripes: uint, f_acc512: XXH3_accumulate_512_f) { @@ -804,7 +804,7 @@ XXH3_accumulate :: #force_inline proc( } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_internal_loop :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8, f_acc512: XXH3_accumulate_512_f, f_scramble: XXH3_scramble_accumulator_f) { @@ -833,14 +833,14 @@ XXH3_hashLong_internal_loop :: #force_inline proc(acc: []xxh_u64, input: []u8, s } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_mix2Accs :: #force_inline proc(acc: []xxh_u64, secret: []u8) -> (res: xxh_u64) { return XXH_mul_64_to_128_fold_64( acc[0] ~ XXH64_read64(secret), acc[1] ~ XXH64_read64(secret[8:])) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_mergeAccs :: #force_inline proc(acc: []xxh_u64, secret: []u8, start: xxh_u64) -> (res: xxh_u64) { result64 := start #no_bounds_check for i := 0; i < 4; i += 1 { @@ -849,7 +849,7 @@ XXH3_mergeAccs :: #force_inline proc(acc: []xxh_u64, secret: []u8, start: xxh_u6 return XXH3_avalanche(result64) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8, f_acc512: XXH3_accumulate_512_f, f_scramble: XXH3_scramble_accumulator_f) -> (hash: xxh_u64) { @@ -868,7 +868,7 @@ XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8, /* It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -880,7 +880,7 @@ XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u This variant enforces that the compiler can detect that, and uses this opportunity to streamline the generated code for better performance. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -896,7 +896,7 @@ XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64, It's important for performance that XXH3_hashLong is not inlined. Not sure why (uop cache maybe?), but the difference is large and easily measurable. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc( input: []u8, seed: xxh_u64, @@ -916,7 +916,7 @@ XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc( /* It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_withSeed_internal(input, seed, XXH3_accumulate_512, XXH3_scramble_accumulator, XXH3_init_custom_secret) } @@ -924,7 +924,7 @@ XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, XXH3_hashLong64_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: xxh_u64) -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLong: XXH3_hashLong64_f) -> (hash: xxh_u64) { assert(len(secret) >= XXH3_SECRET_SIZE_MIN) /* @@ -944,19 +944,19 @@ XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLon } /* === Public entry point === */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64_default :: proc(input: []u8) -> (hash: xxh_u64) { return XXH3_64bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_64b_default) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: xxh_u64) { return XXH3_64bits_internal(input, seed, XXH3_kSecret[:], XXH3_hashLong_64b_withSeed) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH3_64_with_secret :: proc(input, secret: []u8) -> (hash: xxh_u64) { return XXH3_64bits_internal(input, 0, secret, XXH3_hashLong_64b_withSecret) } -XXH3_64 :: proc { XXH3_64_default, XXH3_64_with_seed, XXH3_64_with_secret } \ No newline at end of file +XXH3_64 :: proc { XXH3_64_default, XXH3_64_with_seed, XXH3_64_with_secret } diff --git a/core/hash/xxhash/xxhash_32.odin b/core/hash/xxhash/xxhash_32.odin index b0dea305e..3ea1c3cf2 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -40,7 +40,7 @@ XXH_PRIME32_3 :: 0xC2B2AE3D /*!< 0b11000010101100101010111000111101 */ XXH_PRIME32_4 :: 0x27D4EB2F /*!< 0b00100111110101001110101100101111 */ XXH_PRIME32_5 :: 0x165667B1 /*!< 0b00010110010101100110011110110001 */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_round :: #force_inline proc(seed, input: XXH32_hash) -> (res: XXH32_hash) { seed := seed @@ -53,7 +53,7 @@ XXH32_round :: #force_inline proc(seed, input: XXH32_hash) -> (res: XXH32_hash) /* Mix all bits */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_avalanche :: #force_inline proc(h32: u32) -> (res: u32) { h32 := h32 @@ -65,7 +65,7 @@ XXH32_avalanche :: #force_inline proc(h32: u32) -> (res: u32) { return h32 } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_finalize :: #force_inline proc(h32: u32, buf: []u8, alignment: Alignment) -> (res: u32) { process_1 :: #force_inline proc(h32: u32, buf: []u8) -> (h32_res: u32, buf_res: []u8) { #no_bounds_check b := u32(buf[0]) @@ -143,7 +143,7 @@ XXH32_finalize :: #force_inline proc(h32: u32, buf: []u8, alignment: Alignment) unreachable() } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH32_endian_align :: #force_inline proc(input: []u8, seed := XXH32_DEFAULT_SEED, alignment: Alignment) -> (res: XXH32_hash) { buf := input length := len(input) @@ -318,4 +318,4 @@ XXH32_canonical_from_hash :: proc(hash: XXH32_hash) -> (canonical: XXH32_canonic XXH32_hash_from_canonical :: proc(canonical: ^XXH32_canonical) -> (hash: XXH32_hash) { h := (^u32be)(&canonical.digest)^ return XXH32_hash(h) -} \ No newline at end of file +} diff --git a/core/hash/xxhash/xxhash_64.odin b/core/hash/xxhash/xxhash_64.odin index b274da374..3b24f20a1 100644 --- a/core/hash/xxhash/xxhash_64.odin +++ b/core/hash/xxhash/xxhash_64.odin @@ -40,7 +40,7 @@ XXH_PRIME64_3 :: 0x165667B19E3779F9 /*!< 0b0001011001010110011001111011000110011 XXH_PRIME64_4 :: 0x85EBCA77C2B2AE63 /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */ XXH_PRIME64_5 :: 0x27D4EB2F165667C5 /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_round :: proc(acc, input: xxh_u64) -> (res: xxh_u64) { acc := acc @@ -50,14 +50,14 @@ XXH64_round :: proc(acc, input: xxh_u64) -> (res: xxh_u64) { return acc } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_mergeRound :: proc(acc, val: xxh_u64) -> (res: xxh_u64) { res = acc ~ XXH64_round(0, val) res = res * XXH_PRIME64_1 + XXH_PRIME64_4 return res } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_avalanche :: proc(h64: xxh_u64) -> (res: xxh_u64) { res = h64 res ~= res >> 33 @@ -68,7 +68,7 @@ XXH64_avalanche :: proc(h64: xxh_u64) -> (res: xxh_u64) { return res } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_finalize :: proc(h64: xxh_u64, buf: []u8, alignment: Alignment) -> (res: xxh_u64) { buf := buf length := len(buf) & 31 @@ -100,7 +100,7 @@ XXH64_finalize :: proc(h64: xxh_u64, buf: []u8, alignment: Alignment) -> (res: x return XXH64_avalanche(res) } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_endian_align :: proc(input: []u8, seed := XXH64_DEFAULT_SEED, alignment := Alignment.Unaligned) -> (res: xxh_u64) { buf := input length := len(buf) @@ -191,7 +191,7 @@ XXH64_reset_state :: proc(state_ptr: ^XXH64_state, seed := XXH64_DEFAULT_SEED) - return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_update :: proc(state: ^XXH64_state, input: []u8) -> (err: Error) { buf := input length := len(buf) @@ -245,7 +245,7 @@ XXH64_update :: proc(state: ^XXH64_state, input: []u8) -> (err: Error) { return .None } -@(optimization_mode="speed") +@(optimization_mode="favor_size") XXH64_digest :: proc(state: ^XXH64_state) -> (res: XXH64_hash) { if state.total_len >= 32 { v1 := state.v1 @@ -292,4 +292,4 @@ XXH64_canonical_from_hash :: proc(hash: XXH64_hash) -> (canonical: XXH64_canonic XXH64_hash_from_canonical :: proc(canonical: ^XXH64_canonical) -> (hash: XXH64_hash) { h := (^u64be)(&canonical.digest)^ return XXH64_hash(h) -} \ No newline at end of file +} diff --git a/core/image/bmp/bmp.odin b/core/image/bmp/bmp.odin index a50ad8996..057c2ffa0 100644 --- a/core/image/bmp/bmp.odin +++ b/core/image/bmp/bmp.odin @@ -122,7 +122,7 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context return img, err } -@(optimization_mode="speed") +@(optimization_mode="favor_size") load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { context.allocator = allocator options := options @@ -743,4 +743,4 @@ destroy :: proc(img: ^Image) { @(init, private) _register :: proc() { image.register(.BMP, load_from_bytes, destroy) -} \ No newline at end of file +} diff --git a/core/image/common.odin b/core/image/common.odin index 07152e7db..6ae9850da 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -112,7 +112,8 @@ Image_Option: `.alpha_drop_if_present` If the image has an alpha channel, drop it. - You may want to use `.alpha_premultiply` in this case. + You may want to use `.alpha_ + tiply` in this case. NOTE: For PNG, this also skips handling of the tRNS chunk, if present, unless you select `alpha_premultiply`. @@ -587,6 +588,32 @@ Channel :: enum u8 { A = 4, } +// Take a slice of pixels (`[]RGBA_Pixel`, etc), and return an `Image` +// Don't call `destroy` on the resulting `Image`. Instead, delete the original `pixels` slice. +pixels_to_image :: proc(pixels: [][$N]$E, width: int, height: int) -> (img: Image, ok: bool) where E == u8 || E == u16, N >= 1 && N <= 4 { + if len(pixels) != width * height { + return {}, false + } + + img.height = height + img.width = width + img.depth = 8 when E == u8 else 16 + img.channels = N + + s := transmute(runtime.Raw_Slice)pixels + d := runtime.Raw_Dynamic_Array{ + data = s.data, + len = s.len * size_of(E) * N, + cap = s.len * size_of(E) * N, + allocator = runtime.nil_allocator(), + } + img.pixels = bytes.Buffer{ + buf = transmute([dynamic]u8)d, + } + + return img, true +} + // When you have an RGB(A) image, but want a particular channel. return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) { // Were we actually given a valid image? @@ -1293,6 +1320,42 @@ blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) } blend :: proc{blend_single_channel, blend_pixel} +// For all pixels of the image, multiplies R, G and B by Alpha. This is useful mainly for games rendering anti-aliased transparent sprites. +// Grayscale with alpha images are supported as well. +// Note that some image formats like QOI explicitly do NOT support premultiplied alpha, so you will end up with a non-standard file. +premultiply_alpha :: proc(img: ^Image) -> (ok: bool) { + switch { + case img.channels == 2 && img.depth == 8: + pixels := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u8(u32(pixel.r) * u32(pixel.g) / 0xFF) + } + return true + case img.channels == 2 && img.depth == 16: + pixels := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u16(u32(pixel.r) * u32(pixel.g) / 0xFFFF) + } + return true + case img.channels == 4 && img.depth == 8: + pixels := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u8(u32(pixel.r) * u32(pixel.a) / 0xFF) + pixel.g = u8(u32(pixel.g) * u32(pixel.a) / 0xFF) + pixel.b = u8(u32(pixel.b) * u32(pixel.a) / 0xFF) + } + return true + case img.channels == 4 && img.depth == 16: + pixels := mem.slice_data_cast([]RGBA_Pixel_16, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u16(u32(pixel.r) * u32(pixel.a) / 0xFFFF) + pixel.g = u16(u32(pixel.g) * u32(pixel.a) / 0xFFFF) + pixel.b = u16(u32(pixel.b) * u32(pixel.a) / 0xFFFF) + } + return true + case: return false + } +} // Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate. // Returns early with `false` if already an RGB(A) image. @@ -1376,7 +1439,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo /* Helper functions to read and write data from/to a Context, etc. */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_data :: proc(z: $C, $T: typeid) -> (res: T, err: compress.General_Error) { if r, e := compress.read_data(z, T); e != .None { return {}, .Stream_Too_Short @@ -1385,7 +1448,7 @@ read_data :: proc(z: $C, $T: typeid) -> (res: T, err: compress.General_Error) { } } -@(optimization_mode="speed") +@(optimization_mode="favor_size") read_u8 :: proc(z: $C) -> (res: u8, err: compress.General_Error) { if r, e := compress.read_u8(z); e != .None { return {}, .Stream_Too_Short @@ -1405,4 +1468,4 @@ write_bytes :: proc(buf: ^bytes.Buffer, data: []u8) -> (err: compress.General_Er return .Resize_Failed } return nil -} \ No newline at end of file +} diff --git a/core/image/general_os.odin b/core/image/general_os.odin index 144a3470f..e1fe440a4 100644 --- a/core/image/general_os.odin +++ b/core/image/general_os.odin @@ -27,7 +27,7 @@ which :: proc{ which_file :: proc(path: string) -> Which_File_Type { f, err := os.open(path) - if err != 0 { + if err != nil { return .Unknown } header: [128]byte diff --git a/core/image/png/example.odin b/core/image/png/example.odin index cd9788bd3..ce491978b 100644 --- a/core/image/png/example.odin +++ b/core/image/png/example.odin @@ -213,7 +213,7 @@ write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: b } fd, err := open(filename, flags, mode) - if err != 0 { + if err != nil { return false } defer close(fd) diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index ac61378da..7b6367694 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -450,7 +450,7 @@ when false { } fd, fderr := open(filename, flags, mode) - if fderr != 0 { + if fderr != nil { return .Cannot_Open_File } defer close(fd) diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index 989e7ca6e..5cf252fcc 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -170,7 +170,7 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context return img, err } -@(optimization_mode="speed") +@(optimization_mode="favor_size") load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { context.allocator = allocator options := options @@ -373,4 +373,4 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) { @(init, private) _register :: proc() { image.register(.QOI, load_from_bytes, destroy) -} \ No newline at end of file +} diff --git a/core/io/io.odin b/core/io/io.odin index 961dbe43e..6072aec6d 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -375,10 +375,6 @@ write_at_least :: proc(w: Writer, buf: []byte, min: int) -> (n: int, err: Error) nn, err = write(w, buf[n:]) n += nn } - - if err == nil && n < min { - err = .Short_Write - } return } diff --git a/core/math/cmplx/cmplx.odin b/core/math/cmplx/cmplx.odin index 4625f83c6..d1c70ca61 100644 --- a/core/math/cmplx/cmplx.odin +++ b/core/math/cmplx/cmplx.odin @@ -229,7 +229,7 @@ sqrt_complex128 :: proc "contextless" (x: complex128) -> complex128 { } ln_complex32 :: proc "contextless" (x: complex32) -> complex32 { - return complex(math.ln(abs(x)), phase(x)) + return complex32(ln_complex64(complex64(x))) } ln_complex64 :: proc "contextless" (x: complex64) -> complex64 { return complex(math.ln(abs(x)), phase(x)) @@ -240,26 +240,7 @@ ln_complex128 :: proc "contextless" (x: complex128) -> complex128 { exp_complex32 :: proc "contextless" (x: complex32) -> complex32 { - switch re, im := real(x), imag(x); { - case math.is_inf(re, 0): - switch { - case re > 0 && im == 0: - return x - case math.is_inf(im, 0) || math.is_nan(im): - if re < 0 { - return complex(0, math.copy_sign(0, im)) - } else { - return complex(math.inf_f64(1.0), math.nan_f64()) - } - } - case math.is_nan(re): - if im == 0 { - return complex(math.nan_f16(), im) - } - } - r := math.exp(real(x)) - s, c := math.sincos(imag(x)) - return complex(r*c, r*s) + return complex32(exp_complex64(complex64(x))) } exp_complex64 :: proc "contextless" (x: complex64) -> complex64 { switch re, im := real(x), imag(x); { @@ -308,37 +289,7 @@ exp_complex128 :: proc "contextless" (x: complex128) -> complex128 { pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 { - if x == 0 { // Guaranteed also true for x == -0. - if is_nan(y) { - return nan_complex32() - } - r, i := real(y), imag(y) - switch { - case r == 0: - return 1 - case r < 0: - if i == 0 { - return complex(math.inf_f16(1), 0) - } - return inf_complex32() - case r > 0: - return 0 - } - unreachable() - } - modulus := abs(x) - if modulus == 0 { - return complex(0, 0) - } - r := math.pow(modulus, real(y)) - arg := phase(x) - theta := real(y) * arg - if imag(y) != 0 { - r *= math.exp(-imag(y) * arg) - theta += imag(y) * math.ln(modulus) - } - s, c := math.sincos(theta) - return complex(r*c, r*s) + return complex32(pow_complex64(complex64(x), complex64(y))) } pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 { if x == 0 { // Guaranteed also true for x == -0. @@ -410,7 +361,7 @@ pow_complex128 :: proc "contextless" (x, y: complex128) -> complex128 { log10_complex32 :: proc "contextless" (x: complex32) -> complex32 { - return math.LN10*ln(x) + return complex32(log10_complex64(complex64(x))) } log10_complex64 :: proc "contextless" (x: complex64) -> complex64 { return math.LN10*ln(x) @@ -421,7 +372,7 @@ log10_complex128 :: proc "contextless" (x: complex128) -> complex128 { phase_complex32 :: proc "contextless" (x: complex32) -> f16 { - return math.atan2(imag(x), real(x)) + return f16(phase_complex64(complex64(x))) } phase_complex64 :: proc "contextless" (x: complex64) -> f32 { return math.atan2(imag(x), real(x)) @@ -432,8 +383,7 @@ phase_complex128 :: proc "contextless" (x: complex128) -> f64 { rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 { - s, c := math.sincos(θ) - return complex(r*c, r*s) + return complex32(rect_complex64(f32(r), f32(θ))) } rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 { s, c := math.sincos(θ) diff --git a/core/math/cmplx/cmplx_invtrig.odin b/core/math/cmplx/cmplx_invtrig.odin index b84f0ac9c..40a8493bc 100644 --- a/core/math/cmplx/cmplx_invtrig.odin +++ b/core/math/cmplx/cmplx_invtrig.odin @@ -61,8 +61,7 @@ atanh :: proc{ acos_complex32 :: proc "contextless" (x: complex32) -> complex32 { - w := asin(x) - return complex(math.PI/2 - real(w), -imag(w)) + return complex32(acos_complex64(complex64(x))) } acos_complex64 :: proc "contextless" (x: complex64) -> complex64 { w := asin(x) @@ -75,14 +74,7 @@ acos_complex128 :: proc "contextless" (x: complex128) -> complex128 { acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 { - if x == 0 { - return complex(0, math.copy_sign(math.PI/2, imag(x))) - } - w := acos(x) - if imag(w) <= 0 { - return complex(-imag(w), real(w)) - } - return complex(imag(w), -real(w)) + return complex32(acosh_complex64(complex64(x))) } acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 { if x == 0 { @@ -257,9 +249,7 @@ atan_complex128 :: proc "contextless" (x: complex128) -> complex128 { } atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 { - z := complex(-imag(x), real(x)) // z = i * x - z = atan(z) - return complex(imag(z), -real(z)) // z = -i * z + return complex32(atanh_complex64(complex64(x))) } atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 { z := complex(-imag(x), real(x)) // z = i * x diff --git a/core/math/linalg/general.odin b/core/math/linalg/general.odin index 90fe7332c..7587ad63f 100644 --- a/core/math/linalg/general.odin +++ b/core/math/linalg/general.odin @@ -275,7 +275,8 @@ to_ptr :: proc{vector_to_ptr, matrix_to_ptr} vector_angle_between :: proc "contextless" (a, b: $V/[$N]$E) -> E { a0 := normalize0(a) b0 := normalize0(b) - return math.acos(dot(a0, b0)) + d := clamp(dot(a0, b0), -1, +1) + return math.acos(d) } quaternion64_angle_between :: proc "contextless" (a, b: $Q/quaternion64) -> f16 { c := normalize0(conj(a) * b) diff --git a/core/math/noise/internal.odin b/core/math/noise/internal.odin index b83c4f1b1..bd97bd45c 100644 --- a/core/math/noise/internal.odin +++ b/core/math/noise/internal.odin @@ -688,7 +688,7 @@ _internal_noise_4d_unskewed_base :: proc(seed: i64, coord: Vec4) -> (value: f32) /* Utility functions */ -@(optimization_mode="speed") +@(optimization_mode="favor_size") grad_2d :: proc(seed: i64, svp: [2]i64, delta: [2]f32) -> (value: f32) { hash := seed ~ svp.x ~ svp.y hash *= HASH_MULTIPLIER @@ -698,7 +698,7 @@ grad_2d :: proc(seed: i64, svp: [2]i64, delta: [2]f32) -> (value: f32) { return GRADIENTS_2D[gi] * delta.x + GRADIENTS_2D[gi | 1] * delta.y } -@(optimization_mode="speed") +@(optimization_mode="favor_size") grad_3d :: proc(seed: i64, rvp: [3]i64, delta: [3]f32) -> (value: f32) { hash := (seed ~ rvp.x) ~ (rvp.y ~ rvp.z) hash *= HASH_MULTIPLIER @@ -708,7 +708,7 @@ grad_3d :: proc(seed: i64, rvp: [3]i64, delta: [3]f32) -> (value: f32) { return GRADIENTS_3D[gi] * delta.x + GRADIENTS_3D[gi | 1] * delta.y + GRADIENTS_3D[gi | 2] * delta.z } -@(optimization_mode="speed") +@(optimization_mode="favor_size") grad_4d :: proc(seed: i64, svp: [4]i64, delta: [4]f32) -> (value: f32) { hash := seed ~ (svp.x ~ svp.y) ~ (svp.z ~ svp.w) hash *= HASH_MULTIPLIER @@ -720,13 +720,13 @@ grad_4d :: proc(seed: i64, svp: [4]i64, delta: [4]f32) -> (value: f32) { grad :: proc {grad_2d, grad_3d, grad_4d} -@(optimization_mode="speed") +@(optimization_mode="favor_size") fast_floor :: proc(x: f64) -> (floored: i64) { xi := i64(x) return x < f64(xi) ? xi - 1 : xi } -@(optimization_mode="speed") +@(optimization_mode="favor_size") fast_round :: proc(x: f64) -> (rounded: i64) { return x < 0 ? i64(x - 0.5) : i64(x + 0.5) -} \ No newline at end of file +} diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin index a10ea3238..755e6f86f 100644 --- a/core/math/rand/distributions.odin +++ b/core/math/rand/distributions.odin @@ -8,12 +8,12 @@ float32_uniform :: float32_range // Triangular Distribution // See: http://wikipedia.org/wiki/Triangular_distribution @(require_results) -float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64)) -> f64 { +float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), gen := context.random_generator) -> f64 { if hi-lo == 0 { return lo } lo, hi := lo, hi - u := float64() + u := float64(gen) c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) if u > c { u = 1-u @@ -26,12 +26,12 @@ float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64)) -> f64 { // Triangular Distribution // See: http://wikipedia.org/wiki/Triangular_distribution @(require_results) -float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32)) -> f32 { +float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), gen := context.random_generator) -> f32 { if hi-lo == 0 { return lo } lo, hi := lo, hi - u := float32() + u := float32(gen) c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) if u > c { u = 1-u @@ -44,25 +44,25 @@ float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32)) -> f32 { // Normal/Gaussian Distribution @(require_results) -float64_normal :: proc(mean, stddev: f64) -> f64 { - return norm_float64() * stddev + mean +float64_normal :: proc(mean, stddev: f64, gen := context.random_generator) -> f64 { + return norm_float64(gen) * stddev + mean } // Normal/Gaussian Distribution @(require_results) -float32_normal :: proc(mean, stddev: f32) -> f32 { - return f32(float64_normal(f64(mean), f64(stddev))) +float32_normal :: proc(mean, stddev: f32, gen := context.random_generator) -> f32 { + return f32(float64_normal(f64(mean), f64(stddev), gen)) } // Log Normal Distribution @(require_results) -float64_log_normal :: proc(mean, stddev: f64) -> f64 { - return math.exp(float64_normal(mean, stddev)) +float64_log_normal :: proc(mean, stddev: f64, gen := context.random_generator) -> f64 { + return math.exp(float64_normal(mean, stddev, gen)) } // Log Normal Distribution @(require_results) -float32_log_normal :: proc(mean, stddev: f32) -> f32 { - return f32(float64_log_normal(f64(mean), f64(stddev))) +float32_log_normal :: proc(mean, stddev: f32, gen := context.random_generator) -> f32 { + return f32(float64_log_normal(f64(mean), f64(stddev), gen)) } @@ -72,8 +72,8 @@ float32_log_normal :: proc(mean, stddev: f32) -> f32 { // 0 to positive infinity if lambda > 0 // negative infinity to 0 if lambda <= 0 @(require_results) -float64_exponential :: proc(lambda: f64) -> f64 { - return - math.ln(1 - float64()) / lambda +float64_exponential :: proc(lambda: f64, gen := context.random_generator) -> f64 { + return - math.ln(1 - float64(gen)) / lambda } // Exponential Distribution // `lambda` is 1.0/(desired mean). It should be non-zero. @@ -81,8 +81,8 @@ float64_exponential :: proc(lambda: f64) -> f64 { // 0 to positive infinity if lambda > 0 // negative infinity to 0 if lambda <= 0 @(require_results) -float32_exponential :: proc(lambda: f32) -> f32 { - return f32(float64_exponential(f64(lambda))) +float32_exponential :: proc(lambda: f32, gen := context.random_generator) -> f32 { + return f32(float64_exponential(f64(lambda), gen)) } @@ -96,7 +96,7 @@ float32_exponential :: proc(lambda: f32) -> f32 { // // mean is alpha*beta, variance is math.pow(alpha*beta, 2) @(require_results) -float64_gamma :: proc(alpha, beta: f64) -> f64 { +float64_gamma :: proc(alpha, beta: f64, gen := context.random_generator) -> f64 { if alpha <= 0 || beta <= 0 { panic(#procedure + ": alpha and beta must be > 0.0") } @@ -112,11 +112,11 @@ float64_gamma :: proc(alpha, beta: f64) -> f64 { bbb := alpha - LOG4 ccc := alpha + ainv for { - u1 := float64() + u1 := float64(gen) if !(1e-7 < u1 && u1 < 0.9999999) { continue } - u2 := 1 - float64() + u2 := 1 - float64(gen) v := math.ln(u1 / (1 - u1)) / ainv x := alpha * math.exp(v) z := u1 * u1 * u2 @@ -127,12 +127,12 @@ float64_gamma :: proc(alpha, beta: f64) -> f64 { } case alpha == 1: // float64_exponential(1/beta) - return -math.ln(1 - float64()) * beta + return -math.ln(1 - float64(gen)) * beta case: // ALGORITHM GS of Statistical Computing - Kennedy & Gentle x: f64 for { - u := float64() + u := float64(gen) b := (math.e + alpha) / math.e p := b * u if p <= 1 { @@ -140,7 +140,7 @@ float64_gamma :: proc(alpha, beta: f64) -> f64 { } else { x = -math.ln((b - p) / alpha) } - u1 := float64() + u1 := float64(gen) if p > 1 { if u1 <= math.pow(x, alpha-1) { break @@ -162,8 +162,8 @@ float64_gamma :: proc(alpha, beta: f64) -> f64 { // // mean is alpha*beta, variance is math.pow(alpha*beta, 2) @(require_results) -float32_gamma :: proc(alpha, beta: f32) -> f32 { - return f32(float64_gamma(f64(alpha), f64(beta))) +float32_gamma :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_gamma(f64(alpha), f64(beta), gen)) } @@ -173,14 +173,14 @@ float32_gamma :: proc(alpha, beta: f32) -> f32 { // // Return values range between 0 and 1 @(require_results) -float64_beta :: proc(alpha, beta: f64) -> f64 { +float64_beta :: proc(alpha, beta: f64, gen := context.random_generator) -> f64 { if alpha <= 0 || beta <= 0 { panic(#procedure + ": alpha and beta must be > 0.0") } // Knuth Vol 2 Ed 3 pg 134 "the beta distribution" - y := float64_gamma(alpha, 1.0) + y := float64_gamma(alpha, 1.0, gen) if y != 0 { - return y / (y + float64_gamma(beta, 1.0)) + return y / (y + float64_gamma(beta, 1.0, gen)) } return 0 } @@ -190,35 +190,35 @@ float64_beta :: proc(alpha, beta: f64) -> f64 { // // Return values range between 0 and 1 @(require_results) -float32_beta :: proc(alpha, beta: f32) -> f32 { - return f32(float64_beta(f64(alpha), f64(beta))) +float32_beta :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_beta(f64(alpha), f64(beta), gen)) } // Pareto distribution, `alpha` is the shape parameter. // https://wikipedia.org/wiki/Pareto_distribution @(require_results) -float64_pareto :: proc(alpha: f64) -> f64 { - return math.pow(1 - float64(), -1.0 / alpha) +float64_pareto :: proc(alpha: f64, gen := context.random_generator) -> f64 { + return math.pow(1 - float64(gen), -1.0 / alpha) } // Pareto distribution, `alpha` is the shape parameter. // https://wikipedia.org/wiki/Pareto_distribution @(require_results) -float32_pareto :: proc(alpha, beta: f32) -> f32 { - return f32(float64_pareto(f64(alpha))) +float32_pareto :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_pareto(f64(alpha), gen)) } // Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. @(require_results) -float64_weibull :: proc(alpha, beta: f64) -> f64 { - u := 1 - float64() +float64_weibull :: proc(alpha, beta: f64, gen := context.random_generator) -> f64 { + u := 1 - float64(gen) return alpha * math.pow(-math.ln(u), 1.0/beta) } // Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. @(require_results) -float32_weibull :: proc(alpha, beta: f32) -> f32 { - return f32(float64_weibull(f64(alpha), f64(beta))) +float32_weibull :: proc(alpha, beta: f32, gen := context.random_generator) -> f32 { + return f32(float64_weibull(f64(alpha), f64(beta), gen)) } @@ -227,23 +227,23 @@ float32_weibull :: proc(alpha, beta: f32) -> f32 { // `kappa` is the concentration parameter which must be >= 0 // When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi @(require_results) -float64_von_mises :: proc(mean_angle, kappa: f64) -> f64 { +float64_von_mises :: proc(mean_angle, kappa: f64, gen := context.random_generator) -> f64 { // Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993. mu := mean_angle if kappa <= 1e-6 { - return math.TAU * float64() + return math.TAU * float64(gen) } s := 0.5 / kappa t := s + math.sqrt(1 + s*s) z: f64 for { - u1 := float64() + u1 := float64(gen) z = math.cos(math.TAU * 0.5 * u1) d := z / (t + z) - u2 := float64() + u2 := float64(gen) if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) { break } @@ -251,7 +251,7 @@ float64_von_mises :: proc(mean_angle, kappa: f64) -> f64 { q := 1.0 / t f := (q + z) / (1 + q*z) - u3 := float64() + u3 := float64(gen) if u3 > 0.5 { return math.mod(mu + math.acos(f), math.TAU) } else { @@ -263,57 +263,57 @@ float64_von_mises :: proc(mean_angle, kappa: f64) -> f64 { // `kappa` is the concentration parameter which must be >= 0 // When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi @(require_results) -float32_von_mises :: proc(mean_angle, kappa: f32) -> f32 { - return f32(float64_von_mises(f64(mean_angle), f64(kappa))) +float32_von_mises :: proc(mean_angle, kappa: f32, gen := context.random_generator) -> f32 { + return f32(float64_von_mises(f64(mean_angle), f64(kappa), gen)) } // Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float64_cauchy_lorentz :: proc(x_0, gamma: f64) -> f64 { +float64_cauchy_lorentz :: proc(x_0, gamma: f64, gen := context.random_generator) -> f64 { assert(gamma > 0) // Calculated from the inverse CDF - return math.tan(math.PI * (float64() - 0.5))*gamma + x_0 + return math.tan(math.PI * (float64(gen) - 0.5))*gamma + x_0 } // Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float32_cauchy_lorentz :: proc(x_0, gamma: f32) -> f32 { - return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma))) +float32_cauchy_lorentz :: proc(x_0, gamma: f32, gen := context.random_generator) -> f32 { + return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma), gen)) } // Log Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float64_log_cauchy_lorentz :: proc(x_0, gamma: f64) -> f64 { +float64_log_cauchy_lorentz :: proc(x_0, gamma: f64, gen := context.random_generator) -> f64 { assert(gamma > 0) - return math.exp(math.tan(math.PI * (float64() - 0.5))*gamma + x_0) + return math.exp(math.tan(math.PI * (float64(gen) - 0.5))*gamma + x_0) } // Log Cauchy-Lorentz Distribution // `x_0` is the location, `gamma` is the scale where `gamma` > 0 @(require_results) -float32_log_cauchy_lorentz :: proc(x_0, gamma: f32) -> f32 { - return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma))) +float32_log_cauchy_lorentz :: proc(x_0, gamma: f32, gen := context.random_generator) -> f32 { + return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma), gen)) } // Laplace Distribution // `b` is the scale where `b` > 0 @(require_results) -float64_laplace :: proc(mean, b: f64) -> f64 { +float64_laplace :: proc(mean, b: f64, gen := context.random_generator) -> f64 { assert(b > 0) - p := float64()-0.5 + p := float64(gen)-0.5 return -math.sign(p)*math.ln(1 - 2*abs(p))*b + mean } // Laplace Distribution // `b` is the scale where `b` > 0 @(require_results) -float32_laplace :: proc(mean, b: f32) -> f32 { - return f32(float64_laplace(f64(mean), f64(b))) +float32_laplace :: proc(mean, b: f32, gen := context.random_generator) -> f32 { + return f32(float64_laplace(f64(mean), f64(b), gen)) } @@ -321,18 +321,18 @@ float32_laplace :: proc(mean, b: f32) -> f32 { // `eta` is the shape, `b` is the scale // Both `eta` and `b` must be > 0 @(require_results) -float64_gompertz :: proc(eta, b: f64) -> f64 { +float64_gompertz :: proc(eta, b: f64, gen := context.random_generator) -> f64 { if eta <= 0 || b <= 0 { panic(#procedure + ": eta and b must be > 0.0") } - p := float64() + p := float64(gen) return math.ln(1 - math.ln(1 - p)/eta)/b } // Gompertz Distribution // `eta` is the shape, `b` is the scale // Both `eta` and `b` must be > 0 @(require_results) -float32_gompertz :: proc(eta, b: f32) -> f32 { - return f32(float64_gompertz(f64(eta), f64(b))) +float32_gompertz :: proc(eta, b: f32, gen := context.random_generator) -> f32 { + return f32(float64_gompertz(f64(eta), f64(b), gen)) } diff --git a/core/math/rand/exp.odin b/core/math/rand/exp.odin index f30f11f50..4ceb750da 100644 --- a/core/math/rand/exp.odin +++ b/core/math/rand/exp.odin @@ -16,7 +16,7 @@ import "core:math" // https://www.jstatsoft.org/article/view/v005i08 [web page] // @(require_results) -exp_float64 :: proc() -> f64 { +exp_float64 :: proc(gen := context.random_generator) -> f64 { re :: 7.69711747013104972 @(static, rodata) @@ -199,16 +199,16 @@ exp_float64 :: proc() -> f64 { } for { - j := uint32() + j := uint32(gen) i := j & 0xFF x := f64(j) * f64(we[i]) if j < ke[i] { return x } if i == 0 { - return re - math.ln(float64()) + return re - math.ln(float64(gen)) } - if fe[i]+f32(float64())*(fe[i-1]-fe[i]) < f32(math.exp(-x)) { + if fe[i]+f32(float64(gen))*(fe[i-1]-fe[i]) < f32(math.exp(-x)) { return x } } diff --git a/core/math/rand/normal.odin b/core/math/rand/normal.odin index eefa013df..bc566344c 100644 --- a/core/math/rand/normal.odin +++ b/core/math/rand/normal.odin @@ -18,7 +18,7 @@ import "core:math" // https://www.jstatsoft.org/article/view/v005i08 [web page] // @(require_results) -norm_float64 :: proc() -> f64 { +norm_float64 :: proc(gen := context.random_generator) -> f64 { rn :: 3.442619855899 @(static, rodata) @@ -116,7 +116,7 @@ norm_float64 :: proc() -> f64 { } for { - j := i32(uint32()) + j := i32(uint32(gen)) i := j & 0x7f x := f64(j) * f64(wn[i]) if u32(abs(j)) < kn[i] { @@ -126,15 +126,15 @@ norm_float64 :: proc() -> f64 { if i == 0 { for { - x = -math.ln(float64()) * (1.0/ rn) - y := -math.ln(float64()) + x = -math.ln(float64(gen)) * (1.0/ rn) + y := -math.ln(float64(gen)) if y+y >= x*x { break } } return j > 0 ? rn + x : -rn - x } - if fn[i]+f32(float64())*(fn[i-1]-fn[i]) < f32(math.exp(-0.5*x*x)) { + if fn[i]+f32(float64(gen))*(fn[i-1]-fn[i]) < f32(math.exp(-0.5*x*x)) { return x } } diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 2b7b55096..61301cf8a 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -9,6 +9,10 @@ import "base:runtime" import "core:math" import "core:mem" +Generator :: runtime.Random_Generator + +Generator_Query_Info :: runtime.Random_Generator_Query_Info + Default_Random_State :: runtime.Default_Random_State default_random_generator :: runtime.default_random_generator @@ -54,7 +58,7 @@ Example: import "core:fmt" set_global_seed_example :: proc() { - rand.set_global_seed(1) + rand.reset(1) fmt.println(rand.uint64()) } @@ -62,15 +66,24 @@ Possible Output: 10 */ -reset :: proc(seed: u64) { - runtime.random_generator_reset_u64(context.random_generator, seed) +reset :: proc(seed: u64, gen := context.random_generator) { + runtime.random_generator_reset_u64(gen, seed) +} + + +reset_bytes :: proc(bytes: []byte, gen := context.random_generator) { + runtime.random_generator_reset_bytes(gen, bytes) +} + +query_info :: proc(gen := context.random_generator) -> Generator_Query_Info { + return runtime.random_generator_query_info(gen) } @(private) -_random_u64 :: proc() -> (res: u64) { - ok := runtime.random_generator_read_ptr(context.random_generator, &res, size_of(res)) - assert(ok, "uninitialized context.random_generator") +_random_u64 :: proc(gen := context.random_generator) -> (res: u64) { + ok := runtime.random_generator_read_ptr(gen, &res, size_of(res)) + assert(ok, "uninitialized gen/context.random_generator") return } @@ -95,7 +108,7 @@ Possible Output: */ @(require_results) -uint32 :: proc() -> (val: u32) { return u32(_random_u64()) } +uint32 :: proc(gen := context.random_generator) -> (val: u32) { return u32(_random_u64(gen)) } /* Generates a random 64 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -118,7 +131,7 @@ Possible Output: */ @(require_results) -uint64 :: proc() -> (val: u64) { return _random_u64() } +uint64 :: proc(gen := context.random_generator) -> (val: u64) { return _random_u64(gen) } /* Generates a random 128 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -141,9 +154,9 @@ Possible Output: */ @(require_results) -uint128 :: proc() -> (val: u128) { - a := u128(_random_u64()) - b := u128(_random_u64()) +uint128 :: proc(gen := context.random_generator) -> (val: u128) { + a := u128(_random_u64(gen)) + b := u128(_random_u64(gen)) return (a<<64) | b } @@ -168,7 +181,7 @@ Possible Output: 389 */ -@(require_results) int31 :: proc() -> (val: i32) { return i32(uint32() << 1 >> 1) } +@(require_results) int31 :: proc(gen := context.random_generator) -> (val: i32) { return i32(uint32(gen) << 1 >> 1) } /* Generates a random 63 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -191,7 +204,7 @@ Possible Output: 389 */ -@(require_results) int63 :: proc() -> (val: i64) { return i64(uint64() << 1 >> 1) } +@(require_results) int63 :: proc(gen := context.random_generator) -> (val: i64) { return i64(uint64(gen) << 1 >> 1) } /* Generates a random 127 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -214,7 +227,7 @@ Possible Output: 389 */ -@(require_results) int127 :: proc() -> (val: i128) { return i128(uint128() << 1 >> 1) } +@(require_results) int127 :: proc(gen := context.random_generator) -> (val: i128) { return i128(uint128(gen) << 1 >> 1) } /* Generates a random 31 bit value in the range `[0, n)` using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -242,17 +255,17 @@ Possible Output: */ @(require_results) -int31_max :: proc(n: i32) -> (val: i32) { +int31_max :: proc(n: i32, gen := context.random_generator) -> (val: i32) { if n <= 0 { panic("Invalid argument to int31_max") } if n&(n-1) == 0 { - return int31() & (n-1) + return int31(gen) & (n-1) } max := i32((1<<31) - 1 - (1<<31)%u32(n)) - v := int31() + v := int31(gen) for v > max { - v = int31() + v = int31(gen) } return v % n } @@ -283,17 +296,17 @@ Possible Output: */ @(require_results) -int63_max :: proc(n: i64) -> (val: i64) { +int63_max :: proc(n: i64, gen := context.random_generator) -> (val: i64) { if n <= 0 { panic("Invalid argument to int63_max") } if n&(n-1) == 0 { - return int63() & (n-1) + return int63(gen) & (n-1) } max := i64((1<<63) - 1 - (1<<63)%u64(n)) - v := int63() + v := int63(gen) for v > max { - v = int63() + v = int63(gen) } return v % n } @@ -324,17 +337,17 @@ Possible Output: */ @(require_results) -int127_max :: proc(n: i128) -> (val: i128) { +int127_max :: proc(n: i128, gen := context.random_generator) -> (val: i128) { if n <= 0 { panic("Invalid argument to int127_max") } if n&(n-1) == 0 { - return int127() & (n-1) + return int127(gen) & (n-1) } max := i128((1<<127) - 1 - (1<<127)%u128(n)) - v := int127() + v := int127(gen) for v > max { - v = int127() + v = int127(gen) } return v % n } @@ -365,14 +378,14 @@ Possible Output: */ @(require_results) -int_max :: proc(n: int) -> (val: int) { +int_max :: proc(n: int, gen := context.random_generator) -> (val: int) { if n <= 0 { panic("Invalid argument to int_max") } when size_of(int) == 4 { - return int(int31_max(i32(n))) + return int(int31_max(i32(n), gen)) } else { - return int(int63_max(i64(n))) + return int(int63_max(i64(n), gen)) } } @@ -396,7 +409,7 @@ Possible Output: 0.511 */ -@(require_results) float64 :: proc() -> (val: f64) { return f64(int63_max(1<<53)) / (1 << 53) } +@(require_results) float64 :: proc(gen := context.random_generator) -> (val: f64) { return f64(int63_max(1<<53, gen)) / (1 << 53) } /* Generates a random single floating point value in the range `[0, 1)` using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -418,7 +431,7 @@ Possible Output: 0.511 */ -@(require_results) float32 :: proc() -> (val: f32) { return f32(int31_max(1<<24)) / (1 << 24) } +@(require_results) float32 :: proc(gen := context.random_generator) -> (val: f32) { return f32(int31_max(1<<24, gen)) / (1 << 24) } /* Generates a random double floating point value in the range `[low, high)` using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -446,9 +459,9 @@ Possible Output: 673.130 */ -@(require_results) float64_range :: proc(low, high: f64) -> (val: f64) { +@(require_results) float64_range :: proc(low, high: f64, gen := context.random_generator) -> (val: f64) { assert(low <= high, "low must be lower than or equal to high") - val = (high-low)*float64() + low + val = (high-low)*float64(gen) + low if val >= high { val = max(low, high * (1 - math.F64_EPSILON)) } @@ -481,9 +494,9 @@ Possible Output: 673.130 */ -@(require_results) float32_range :: proc(low, high: f32) -> (val: f32) { +@(require_results) float32_range :: proc(low, high: f32, gen := context.random_generator) -> (val: f32) { assert(low <= high, "low must be lower than or equal to high") - val = (high-low)*float32() + low + val = (high-low)*float32(gen) + low if val >= high { val = max(low, high * (1 - math.F32_EPSILON)) } @@ -518,12 +531,12 @@ Possible Output: */ @(require_results) -read :: proc(p: []byte) -> (n: int) { +read :: proc(p: []byte, gen := context.random_generator) -> (n: int) { pos := i8(0) val := i64(0) for n = 0; n < len(p); n += 1 { if pos == 0 { - val = int63() + val = int63(gen) pos = 7 } p[n] = byte(val) @@ -566,10 +579,10 @@ Possible Output: */ @(require_results) -perm :: proc(n: int, allocator := context.allocator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error { +perm :: proc(n: int, allocator := context.allocator, gen := context.random_generator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error { m := make([]int, n, allocator) or_return for i := 0; i < n; i += 1 { - j := int_max(i+1) + j := int_max(i+1, gen) m[i] = m[j] m[j] = i } @@ -599,14 +612,20 @@ Possible Output: [2, 4, 3, 1] */ -shuffle :: proc(array: $T/[]$E) { +shuffle :: proc(array: $T/[]$E, gen := context.random_generator) { n := i64(len(array)) if n < 2 { return } - for i := i64(n - 1); i > 0; i -= 1 { - j := int63_max(i + 1) + i := n - 1 + for ; i > (1<<31 - 2); i -= 1 { + j := int63_max(i + 1, gen) + array[i], array[j] = array[j], array[i] + } + + for ; i > 0; i -= 1 { + j := int31_max(i32(i + 1), gen) array[i], array[j] = array[j], array[i] } } @@ -641,17 +660,17 @@ Possible Output: */ @(require_results) -choice :: proc(array: $T/[]$E) -> (res: E) { +choice :: proc(array: $T/[]$E, gen := context.random_generator) -> (res: E) { n := i64(len(array)) if n < 1 { return E{} } - return array[int63_max(n)] + return array[int63_max(n, gen)] } @(require_results) -choice_enum :: proc($T: typeid) -> T +choice_enum :: proc($T: typeid, gen := context.random_generator) -> T where intrinsics.type_is_enum(T), size_of(T) <= 8, @@ -659,11 +678,11 @@ choice_enum :: proc($T: typeid) -> T { when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) && u64(max(T)) > u64(max(i64)) { - i := uint64() % u64(len(T)) + i := uint64(gen) % u64(len(T)) i += u64(min(T)) return T(i) } else { - i := int63_max(i64(len(T))) + i := int63_max(i64(len(T)), gen) i += i64(min(T)) return T(i) } diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 1d79e09c1..a5b93ad05 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -748,9 +748,7 @@ dynamic_pool_alloc_bytes :: proc(p: ^Dynamic_Pool, bytes: int) -> ([]byte, Alloc return } - n := bytes - extra := p.alignment - (n % p.alignment) - n += extra + n := align_formula(bytes, p.alignment) if n > p.block_size { return nil, .Invalid_Argument } diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin index 1c7c326eb..cac151183 100644 --- a/core/mem/tlsf/tlsf_internal.odin +++ b/core/mem/tlsf/tlsf_internal.odin @@ -284,7 +284,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: // TLSF utility functions. In most cases these are direct translations of // the documentation in the research paper. -@(optimization_mode="speed", require_results) +@(optimization_mode="favor_size", require_results) mapping_insert :: proc(size: uint) -> (fl, sl: i32) { if size < SMALL_BLOCK_SIZE { // Store small blocks in first list. @@ -297,7 +297,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) { return } -@(optimization_mode="speed", require_results) +@(optimization_mode="favor_size", require_results) mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { rounded = size if size >= SMALL_BLOCK_SIZE { @@ -308,7 +308,7 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { } // This version rounds up to the next block size (for allocations) -@(optimization_mode="speed", require_results) +@(optimization_mode="favor_size", require_results) mapping_search :: proc(size: uint) -> (fl, sl: i32) { return mapping_insert(mapping_round(size)) } diff --git a/core/mem/virtual/file.odin b/core/mem/virtual/file.odin index 5d7180588..2f852b40c 100644 --- a/core/mem/virtual/file.odin +++ b/core/mem/virtual/file.odin @@ -24,7 +24,7 @@ map_file :: proc{ map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { fd, err := os.open(filename, os.O_RDWR) - if err != 0 { + if err != nil { return nil, .Open_Failure } defer os.close(fd) @@ -34,7 +34,7 @@ map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: [] map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { size, os_err := os.file_size(os.Handle(fd)) - if os_err != 0 { + if os_err != nil { return nil, .Stat_Failure } if size < 0 { diff --git a/core/mem/virtual/virtual_bsd.odin b/core/mem/virtual/virtual_other.odin similarity index 92% rename from core/mem/virtual/virtual_bsd.odin rename to core/mem/virtual/virtual_other.odin index 12e7818ef..96d9683c4 100644 --- a/core/mem/virtual/virtual_bsd.odin +++ b/core/mem/virtual/virtual_other.odin @@ -1,5 +1,7 @@ -//+build freebsd, openbsd, netbsd //+private +//+build !darwin +//+build !linux +//+build !windows package mem_virtual _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index fc422b3a9..10069963a 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -53,9 +53,9 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so unreachable() } - sock, ok := os.socket(c_family, c_type, c_protocol) - if ok != os.ERROR_NONE { - err = Create_Socket_Error(ok) + sock, sock_err := os.socket(c_family, c_type, c_protocol) + if sock_err != nil { + err = Create_Socket_Error(os.is_platform_error(sock_err) or_else -1) return } @@ -84,8 +84,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) - if res != os.ERROR_NONE { - err = Dial_Error(res) + if res != nil { + err = Dial_Error(os.is_platform_error(res) or_else -1) return } @@ -100,11 +100,11 @@ _bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) - if res != os.ERROR_NONE { + if res != nil { if res == os.EACCES && ep.port <= MAX_PRIVILEGED_PORT { err = .Privileged_Port_Without_Root } else { - err = Bind_Error(res) + err = Bind_Error(os.is_platform_error(res) or_else -1) } } return @@ -128,8 +128,8 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ bind(sock, interface_endpoint) or_return res := os.listen(os.Socket(skt), backlog) - if res != os.ERROR_NONE { - err = Listen_Error(res) + if res != nil { + err = Listen_Error(os.is_platform_error(res) or_else -1) return } @@ -141,9 +141,9 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) - client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) - if ok != os.ERROR_NONE { - err = Accept_Error(ok) + client_sock, client_sock_err := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) + if client_sock_err != nil { + err = Accept_Error(os.is_platform_error(client_sock_err) or_else -1) return } client = TCP_Socket(client_sock) @@ -162,9 +162,9 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ if len(buf) <= 0 { return } - res, ok := os.recv(os.Socket(skt), buf, 0) - if ok != os.ERROR_NONE { - err = TCP_Recv_Error(ok) + res, res_err := os.recv(os.Socket(skt), buf, 0) + if res_err != nil { + err = TCP_Recv_Error(os.is_platform_error(res_err) or_else -1) return } return int(res), nil @@ -178,9 +178,9 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp from: os.SOCKADDR_STORAGE_LH fromsize := c.int(size_of(from)) - res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) - if ok != os.ERROR_NONE { - err = UDP_Recv_Error(ok) + res, res_err := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) + if res_err != nil { + err = UDP_Recv_Error(os.is_platform_error(res_err) or_else -1) return } @@ -194,9 +194,9 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, ok := os.send(os.Socket(skt), remaining, 0) - if ok != os.ERROR_NONE { - err = TCP_Send_Error(ok) + res, res_err := os.send(os.Socket(skt), remaining, 0) + if res_err != nil { + err = TCP_Send_Error(os.is_platform_error(res_err) or_else -1) return } bytes_written += int(res) @@ -210,9 +210,9 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: for bytes_written < len(buf) { limit := min(1<<31, len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) - if ok != os.ERROR_NONE { - err = UDP_Send_Error(ok) + res, res_err := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) + if res_err != nil { + err = UDP_Send_Error(os.is_platform_error(res_err) or_else -1) return } bytes_written += int(res) @@ -224,8 +224,8 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: _shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(skt) res := os.shutdown(os.Socket(s), int(manner)) - if res != os.ERROR_NONE { - return Shutdown_Error(res) + if res != nil { + return Shutdown_Error(os.is_platform_error(res) or_else -1) } return } @@ -302,8 +302,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca skt := any_socket_to_socket(s) res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len) - if res != os.ERROR_NONE { - return Socket_Option_Error(res) + if res != nil { + return Socket_Option_Error(os.is_platform_error(res) or_else -1) } return nil @@ -314,8 +314,8 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E socket := any_socket_to_socket(socket) flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0) - if getfl_err != os.ERROR_NONE { - return Set_Blocking_Error(getfl_err) + if getfl_err != nil { + return Set_Blocking_Error(os.is_platform_error(getfl_err) or_else -1) } if should_block { @@ -325,8 +325,8 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E } _, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags) - if setfl_err != os.ERROR_NONE { - return Set_Blocking_Error(setfl_err) + if setfl_err != nil { + return Set_Blocking_Error(os.is_platform_error(setfl_err) or_else -1) } return nil diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index a5d553234..350d3947c 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -117,7 +117,7 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) { family := _unwrap_os_family(family) proto, socktype := _unwrap_os_proto_socktype(protocol) - sock, errno := linux.socket(family, socktype, {}, proto) + sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto) if errno != .NONE { return {}, Create_Socket_Error(errno) } @@ -132,7 +132,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } // Create new TCP socket os_sock: linux.Fd - os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {}, .TCP) + os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket return {}, Create_Socket_Error(errno) @@ -172,7 +172,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network ep_address := _unwrap_os_addr(endpoint) // Create TCP socket os_sock: linux.Fd - os_sock, errno = linux.socket(ep_family, .STREAM, {}, .TCP) + os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket return {}, Create_Socket_Error(errno) diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 92d00b47c..31e8fdd53 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -599,6 +599,7 @@ Field_Flag :: enum { Subtype, By_Ptr, No_Broadcast, + No_Capture, Results, Tags, @@ -619,6 +620,7 @@ field_flag_strings := [Field_Flag]string{ .Subtype = "#subtype", .By_Ptr = "#by_ptr", .No_Broadcast = "#no_broadcast", + .No_Capture = "#no_capture", .Results = "results", .Tags = "field tag", @@ -634,6 +636,7 @@ field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{ {"subtype", .Subtype}, {"by_ptr", .By_Ptr}, {"no_broadcast", .No_Broadcast}, + {"no_capture", .No_Capture}, } @@ -754,7 +757,7 @@ Array_Type :: struct { using node: Expr, open: tokenizer.Pos, tag: ^Expr, - len: ^Expr, // Ellipsis node for [?]T array types, nil for slice types + len: ^Expr, // Unary_Expr node for [?]T array types, nil for slice types close: tokenizer.Pos, elem: ^Expr, } diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index dec892f84..24c85a19e 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1778,6 +1778,7 @@ parse_var_type :: proc(p: ^Parser, flags: ast.Field_Flags) -> ^ast.Expr { type = ast.new(ast.Bad_Expr, tok.pos, end_pos(tok)) } e := ast.new(ast.Ellipsis, type.pos, type) + e.tok = tok.kind e.expr = type return e } @@ -2179,22 +2180,25 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ } } - #partial switch e in ast.strip_or_return_expr(expr).derived_expr { - case ^ast.Proc_Lit: - if e.inlining != .None && e.inlining != pi { - error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") + if expr != nil { + #partial switch e in ast.strip_or_return_expr(expr).derived_expr { + case ^ast.Proc_Lit: + if e.inlining != .None && e.inlining != pi { + error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") + } + e.inlining = pi + return expr + case ^ast.Call_Expr: + if e.inlining != .None && e.inlining != pi { + error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") + } + e.inlining = pi + return expr } - e.inlining = pi - case ^ast.Call_Expr: - if e.inlining != .None && e.inlining != pi { - error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") - } - e.inlining = pi - case: - error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text) - return ast.new(ast.Bad_Expr, tok.pos, expr) } - return expr + + error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text) + return ast.new(ast.Bad_Expr, tok.pos, expr) } parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { @@ -2258,18 +2262,18 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { hp.type = type return hp - case "file", "line", "procedure", "caller_location": + case "file", "directory", "line", "procedure", "caller_location": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok bd.name = name.text return bd - case "location", "load", "assert", "defined", "config": + + case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok bd.name = name.text return parse_call_expr(p, bd) - case "soa": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok diff --git a/core/os/dir_bsd.odin b/core/os/dir_bsd.odin deleted file mode 100644 index c0dc8ad1f..000000000 --- a/core/os/dir_bsd.odin +++ /dev/null @@ -1,73 +0,0 @@ -//+build freebsd, netbsd -package os - -import "core:mem" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - fullpath := make([]byte, len(dirpath)+1+len(filename), context.temp_allocator) - copy(fullpath, dirpath) - copy(fullpath[len(dirpath):], "/") - copy(fullpath[len(dirpath)+1:], filename) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(string(fullpath), allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_darwin.odin b/core/os/dir_darwin.odin deleted file mode 100644 index 7d0f2936d..000000000 --- a/core/os/dir_darwin.odin +++ /dev/null @@ -1,69 +0,0 @@ -package os - -import "core:strings" -import "core:mem" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(fullpath, allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_linux.odin b/core/os/dir_linux.odin deleted file mode 100644 index 3a51d7c70..000000000 --- a/core/os/dir_linux.odin +++ /dev/null @@ -1,72 +0,0 @@ -package os - -import "core:strings" -import "core:mem" -import "base:runtime" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(fullpath, allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_openbsd.odin b/core/os/dir_openbsd.odin deleted file mode 100644 index 465fd35ae..000000000 --- a/core/os/dir_openbsd.odin +++ /dev/null @@ -1,71 +0,0 @@ -package os - -import "core:strings" -import "core:mem" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - // XXX OpenBSD - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(fullpath, allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin new file mode 100644 index 000000000..6f6bed36d --- /dev/null +++ b/core/os/dir_unix.odin @@ -0,0 +1,62 @@ +//+build darwin, linux, netbsd, freebsd, openbsd +package os + +import "core:strings" + +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { + dirp := _fdopendir(fd) or_return + defer _closedir(dirp) + + dirpath := absolute_path_from_handle(fd) or_return + defer delete(dirpath) + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + dfi := make([dynamic]File_Info, 0, size, allocator) or_return + defer if err != nil { + for fi_ in dfi { + file_info_delete(fi_, allocator) + } + delete(dfi) + } + + for { + entry: Dirent + end_of_stream: bool + entry, err, end_of_stream = _readdir(dirp) + if err != nil { + return + } else if end_of_stream { + break + } + + fi_: File_Info + filename := string(cstring(&entry.name[0])) + + if filename == "." || filename == ".." { + continue + } + + fullpath := strings.join({ dirpath, filename }, "/", allocator) + + s: OS_Stat + s, err = _lstat(fullpath) + if err != nil { + delete(fullpath, allocator) + return + } + _fill_file_info_from_stat(&fi_, s) + fi_.fullpath = fullpath + fi_.name = path_base(fi_.fullpath) + + append(&dfi, fi_) + } + + return dfi[:], nil +} diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 9ca78948e..ae3e6922c 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -4,7 +4,9 @@ import win32 "core:sys/windows" import "core:strings" import "base:runtime" -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { + @(require_results) find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) { // Ignore "." and ".." if d.cFileName[0] == '.' && d.cFileName[1] == 0 { @@ -57,7 +59,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F dir_fi, _ := file_info_from_get_file_information_by_handle("", h) if !dir_fi.is_dir { - return nil, ERROR_FILE_IS_NOT_DIR + return nil, .Not_Dir } n := n @@ -68,15 +70,14 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - wpath: []u16 - wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator) - if len(wpath) == 0 || err != ERROR_NONE { + wpath := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return + if len(wpath) == 0 { return } - dfi := make([dynamic]File_Info, 0, size) + dfi := make([dynamic]File_Info, 0, size) or_return - wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) + wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return copy(wpath_search, wpath) wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+1] = '*' @@ -88,7 +89,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F find_data := &win32.WIN32_FIND_DATAW{} find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) if find_handle == win32.INVALID_HANDLE_VALUE { - err = Errno(win32.GetLastError()) + err = get_last_error() return dfi[:], err } defer win32.FindClose(find_handle) @@ -101,7 +102,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F } if !win32.FindNextFileW(find_handle, find_data) { - e := Errno(win32.GetLastError()) + e := get_last_error() if e == ERROR_NO_MORE_FILES { break } @@ -109,5 +110,5 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F } } - return dfi[:], ERROR_NONE + return dfi[:], nil } diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index 0e3c7f04a..efd002342 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -7,27 +7,22 @@ import "base:runtime" // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { if key == "" { return } wkey := win32.utf8_to_wstring(key) n := win32.GetEnvironmentVariableW(wkey, nil, 0) - if n == 0 { - err := win32.GetLastError() - if err == u32(ERROR_ENVVAR_NOT_FOUND) { - return "", false - } + if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", false } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - b := make([dynamic]u16, n, context.temp_allocator) + b, _ := make([dynamic]u16, n, context.temp_allocator) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) - if n == 0 { - err := win32.GetLastError() - if err == u32(ERROR_ENVVAR_NOT_FOUND) { - return "", false - } + if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", false } value, _ = win32.utf16_to_utf8(b[:n], allocator) found = true @@ -39,41 +34,46 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin // It returns the value, which will be empty if the variable is not present // To distinguish between an empty value and an unset value, use lookup_env // NOTE: the value will be allocated with the supplied allocator +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } // set_env sets the value of the environment variable named by the key -set_env :: proc(key, value: string) -> Errno { +set_env :: proc(key, value: string) -> Error { k := win32.utf8_to_wstring(key) v := win32.utf8_to_wstring(value) if !win32.SetEnvironmentVariableW(k, v) { - return Errno(win32.GetLastError()) + return get_last_error() } - return 0 + return nil } // unset_env unsets a single environment variable -unset_env :: proc(key: string) -> Errno { +unset_env :: proc(key: string) -> Error { k := win32.utf8_to_wstring(key) if !win32.SetEnvironmentVariableW(k, nil) { - return Errno(win32.GetLastError()) + return get_last_error() } - return 0 + return nil } // environ returns a copy of strings representing the environment, in the form "key=value" // NOTE: the slice of strings and the strings with be allocated using the supplied allocator +@(require_results) environ :: proc(allocator := context.allocator) -> []string { - envs := cast([^]win32.WCHAR)(win32.GetEnvironmentStringsW()) + envs := ([^]win32.WCHAR)(win32.GetEnvironmentStringsW()) if envs == nil { return nil } defer win32.FreeEnvironmentStringsW(envs) - r := make([dynamic]string, 0, 50, allocator) + r, err := make([dynamic]string, 0, 50, allocator) + if err != nil { + return nil + } for from, i := 0, 0; true; i += 1 { if c := envs[i]; c == 0 { if i <= from { diff --git a/core/os/errors.odin b/core/os/errors.odin new file mode 100644 index 000000000..691397f4b --- /dev/null +++ b/core/os/errors.odin @@ -0,0 +1,322 @@ +package os + +import "base:intrinsics" +import "base:runtime" +import "core:io" + +Platform_Error :: _Platform_Error +#assert(size_of(Platform_Error) <= 4) +#assert(intrinsics.type_has_nil(Platform_Error)) + +General_Error :: enum u32 { + None, + + Permission_Denied, + Exist, + Not_Exist, + Closed, + + Timeout, + + Broken_Pipe, + + // Indicates that an attempt to retrieve a file's size was made, but the + // file doesn't have a size. + No_Size, + + Invalid_File, + Invalid_Dir, + Invalid_Path, + Invalid_Callback, + + Pattern_Has_Separator, + + Unsupported, + + File_Is_Pipe, + Not_Dir, +} + + +Errno :: Error // alias for legacy use + +Error :: union #shared_nil { + General_Error, + io.Error, + runtime.Allocator_Error, + Platform_Error, +} +#assert(size_of(Error) == 8) + +ERROR_NONE :: Error{} + +ERROR_EOF :: io.Error.EOF + +@(require_results) +is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) { + v := ferr.(Platform_Error) or_else {} + return i32(v), i32(v) != 0 +} + +@(require_results) +error_string :: proc "contextless" (ferr: Error) -> string { + if ferr == nil { + return "" + } + switch e in ferr { + case General_Error: + switch e { + case .None: return "" + case .Permission_Denied: return "permission denied" + case .Exist: return "file already exists" + case .Not_Exist: return "file does not exist" + case .Closed: return "file already closed" + case .Timeout: return "i/o timeout" + case .Broken_Pipe: return "Broken pipe" + case .No_Size: return "file has no definite size" + case .Invalid_File: return "invalid file" + case .Invalid_Dir: return "invalid directory" + case .Invalid_Path: return "invalid path" + case .Invalid_Callback: return "invalid callback" + case .Unsupported: return "unsupported" + case .Pattern_Has_Separator: return "pattern has separator" + case .File_Is_Pipe: return "file is pipe" + case .Not_Dir: return "file is not directory" + } + case io.Error: + switch e { + case .None: return "" + case .EOF: return "eof" + case .Unexpected_EOF: return "unexpected eof" + case .Short_Write: return "short write" + case .Invalid_Write: return "invalid write result" + case .Short_Buffer: return "short buffer" + case .No_Progress: return "multiple read calls return no data or error" + case .Invalid_Whence: return "invalid whence" + case .Invalid_Offset: return "invalid offset" + case .Invalid_Unread: return "invalid unread" + case .Negative_Read: return "negative read" + case .Negative_Write: return "negative write" + case .Negative_Count: return "negative count" + case .Buffer_Full: return "buffer full" + case .Unknown, .Empty: // + } + case runtime.Allocator_Error: + switch e { + case .None: return "" + case .Out_Of_Memory: return "out of memory" + case .Invalid_Pointer: return "invalid allocator pointer" + case .Invalid_Argument: return "invalid allocator argument" + case .Mode_Not_Implemented: return "allocator mode not implemented" + } + case Platform_Error: + return _error_string(e) + } + + return "unknown error" +} + +print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) { + err_str := error_string(ferr) + + // msg + ": " + err_str + '\n' + length := len(msg) + 2 + len(err_str) + 1 + buf_ := intrinsics.alloca(length, 1) + buf := buf_[:length] + + copy(buf, msg) + buf[len(msg)] = ':' + buf[len(msg) + 1] = ' ' + copy(buf[len(msg) + 2:], err_str) + buf[length - 1] = '\n' + return write(f, buf) +} + + +@(require_results, private) +_error_string :: proc "contextless" (e: Platform_Error) -> string where intrinsics.type_is_enum(Platform_Error) { + if e == nil { + return "" + } + + when ODIN_OS == .Darwin { + if s := string(_darwin_string_error(i32(e))); s != "" { + return s + } + } + + when ODIN_OS != .Linux { + @(require_results) + binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check { + n := len(array) + left, right := 0, n + for left < right { + mid := int(uint(left+right) >> 1) + if array[mid] < key { + left = mid+1 + } else { + // equal or greater + right = mid + } + } + return left, left < n && array[left] == key + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := binary_search(ti.values, err); ok { + return ti.names[idx] + } + } else { + @(rodata, static) + pe_strings := [Platform_Error]string{ + .NONE = "", + .EPERM = "Operation not permitted", + .ENOENT = "No such file or directory", + .ESRCH = "No such process", + .EINTR = "Interrupted system call", + .EIO = "Input/output error", + .ENXIO = "No such device or address", + .E2BIG = "Argument list too long", + .ENOEXEC = "Exec format error", + .EBADF = "Bad file descriptor", + .ECHILD = "No child processes", + .EAGAIN = "Resource temporarily unavailable", + .ENOMEM = "Cannot allocate memory", + .EACCES = "Permission denied", + .EFAULT = "Bad address", + .ENOTBLK = "Block device required", + .EBUSY = "Device or resource busy", + .EEXIST = "File exists", + .EXDEV = "Invalid cross-device link", + .ENODEV = "No such device", + .ENOTDIR = "Not a directory", + .EISDIR = "Is a directory", + .EINVAL = "Invalid argument", + .ENFILE = "Too many open files in system", + .EMFILE = "Too many open files", + .ENOTTY = "Inappropriate ioctl for device", + .ETXTBSY = "Text file busy", + .EFBIG = "File too large", + .ENOSPC = "No space left on device", + .ESPIPE = "Illegal seek", + .EROFS = "Read-only file system", + .EMLINK = "Too many links", + .EPIPE = "Broken pipe", + .EDOM = "Numerical argument out of domain", + .ERANGE = "Numerical result out of range", + .EDEADLK = "Resource deadlock avoided", + .ENAMETOOLONG = "File name too long", + .ENOLCK = "No locks available", + .ENOSYS = "Function not implemented", + .ENOTEMPTY = "Directory not empty", + .ELOOP = "Too many levels of symbolic links", + .EUNKNOWN_41 = "Unknown Error (41)", + .ENOMSG = "No message of desired type", + .EIDRM = "Identifier removed", + .ECHRNG = "Channel number out of range", + .EL2NSYNC = "Level 2 not synchronized", + .EL3HLT = "Level 3 halted", + .EL3RST = "Level 3 reset", + .ELNRNG = "Link number out of range", + .EUNATCH = "Protocol driver not attached", + .ENOCSI = "No CSI structure available", + .EL2HLT = "Level 2 halted", + .EBADE = "Invalid exchange", + .EBADR = "Invalid request descriptor", + .EXFULL = "Exchange full", + .ENOANO = "No anode", + .EBADRQC = "Invalid request code", + .EBADSLT = "Invalid slot", + .EUNKNOWN_58 = "Unknown Error (58)", + .EBFONT = "Bad font file format", + .ENOSTR = "Device not a stream", + .ENODATA = "No data available", + .ETIME = "Timer expired", + .ENOSR = "Out of streams resources", + .ENONET = "Machine is not on the network", + .ENOPKG = "Package not installed", + .EREMOTE = "Object is remote", + .ENOLINK = "Link has been severed", + .EADV = "Advertise error", + .ESRMNT = "Srmount error", + .ECOMM = "Communication error on send", + .EPROTO = "Protocol error", + .EMULTIHOP = "Multihop attempted", + .EDOTDOT = "RFS specific error", + .EBADMSG = "Bad message", + .EOVERFLOW = "Value too large for defined data type", + .ENOTUNIQ = "Name not unique on network", + .EBADFD = "File descriptor in bad state", + .EREMCHG = "Remote address changed", + .ELIBACC = "Can not access a needed shared library", + .ELIBBAD = "Accessing a corrupted shared library", + .ELIBSCN = ".lib section in a.out corrupted", + .ELIBMAX = "Attempting to link in too many shared libraries", + .ELIBEXEC = "Cannot exec a shared library directly", + .EILSEQ = "Invalid or incomplete multibyte or wide character", + .ERESTART = "Interrupted system call should be restarted", + .ESTRPIPE = "Streams pipe error", + .EUSERS = "Too many users", + .ENOTSOCK = "Socket operation on non-socket", + .EDESTADDRREQ = "Destination address required", + .EMSGSIZE = "Message too long", + .EPROTOTYPE = "Protocol wrong type for socket", + .ENOPROTOOPT = "Protocol not available", + .EPROTONOSUPPORT = "Protocol not supported", + .ESOCKTNOSUPPORT = "Socket type not supported", + .EOPNOTSUPP = "Operation not supported", + .EPFNOSUPPORT = "Protocol family not supported", + .EAFNOSUPPORT = "Address family not supported by protocol", + .EADDRINUSE = "Address already in use", + .EADDRNOTAVAIL = "Cannot assign requested address", + .ENETDOWN = "Network is down", + .ENETUNREACH = "Network is unreachable", + .ENETRESET = "Network dropped connection on reset", + .ECONNABORTED = "Software caused connection abort", + .ECONNRESET = "Connection reset by peer", + .ENOBUFS = "No buffer space available", + .EISCONN = "Transport endpoint is already connected", + .ENOTCONN = "Transport endpoint is not connected", + .ESHUTDOWN = "Cannot send after transport endpoint shutdown", + .ETOOMANYREFS = "Too many references: cannot splice", + .ETIMEDOUT = "Connection timed out", + .ECONNREFUSED = "Connection refused", + .EHOSTDOWN = "Host is down", + .EHOSTUNREACH = "No route to host", + .EALREADY = "Operation already in progress", + .EINPROGRESS = "Operation now in progress", + .ESTALE = "Stale file handle", + .EUCLEAN = "Structure needs cleaning", + .ENOTNAM = "Not a XENIX named type file", + .ENAVAIL = "No XENIX semaphores available", + .EISNAM = "Is a named type file", + .EREMOTEIO = "Remote I/O error", + .EDQUOT = "Disk quota exceeded", + .ENOMEDIUM = "No medium found", + .EMEDIUMTYPE = "Wrong medium type", + .ECANCELED = "Operation canceled", + .ENOKEY = "Required key not available", + .EKEYEXPIRED = "Key has expired", + .EKEYREVOKED = "Key has been revoked", + .EKEYREJECTED = "Key was rejected by service", + .EOWNERDEAD = "Owner died", + .ENOTRECOVERABLE = "State not recoverable", + .ERFKILL = "Operation not possible due to RF-kill", + .EHWPOISON = "Memory page has hardware error", + } + if Platform_Error.NONE <= e && e <= max(Platform_Error) { + return pe_strings[e] + } + } + return "" +} + +@(private, require_results) +error_to_io_error :: proc(ferr: Error) -> io.Error { + if ferr == nil { + return .None + } + return ferr.(io.Error) or_else .Unknown +} diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index 3efe30d17..3f6f781aa 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -5,13 +5,15 @@ import "base:intrinsics" import "base:runtime" import "core:unicode/utf16" +@(require_results) is_path_separator :: proc(c: byte) -> bool { return c == '/' || c == '\\' } -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { if len(path) == 0 { - return INVALID_HANDLE, ERROR_FILE_NOT_FOUND + return INVALID_HANDLE, General_Error.Not_Exist } access: u32 @@ -52,32 +54,31 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn wide_path := win32.utf8_to_wstring(path) handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil)) if handle != INVALID_HANDLE { - return handle, ERROR_NONE + return handle, nil } - err := Errno(win32.GetLastError()) - return INVALID_HANDLE, err + return INVALID_HANDLE, get_last_error() } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { if !win32.CloseHandle(win32.HANDLE(fd)) { - return Errno(win32.GetLastError()) + return get_last_error() } - return ERROR_NONE + return nil } -flush :: proc(fd: Handle) -> (err: Errno) { +flush :: proc(fd: Handle) -> (err: Error) { if !win32.FlushFileBuffers(win32.HANDLE(fd)) { - err = Errno(win32.GetLastError()) + err = get_last_error() } return } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } single_write_length: win32.DWORD @@ -90,25 +91,24 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) { e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) if single_write_length <= 0 || !e { - err := Errno(win32.GetLastError()) - return int(total_write), err + return int(total_write), get_last_error() } total_write += i64(single_write_length) } - return int(total_write), ERROR_NONE + return int(total_write), nil } -@(private="file") -read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) { +@(private="file", require_results) +read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { - return 0, 0 + return 0, nil } BUF_SIZE :: 386 buf16: [BUF_SIZE]u16 buf8: [4*BUF_SIZE]u8 - for n < len(b) && err == 0 { + for n < len(b) && err == nil { min_read := max(len(b)/4, 1 if len(b) > 0 else 0) max_read := u32(min(BUF_SIZE, min_read)) if max_read == 0 { @@ -118,14 +118,14 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) { single_read_length: u32 ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) if !ok { - err = Errno(win32.GetLastError()) + err = get_last_error() } buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) src := buf8[:buf8_len] ctrl_z := false - for i := 0; i < len(src) && n+i < len(b); i += 1 { + for i := 0; i < len(src) && n < len(b); i += 1 { x := src[i] if x == 0x1a { // ctrl-z ctrl_z = true @@ -149,9 +149,9 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) { return } -read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) { +read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } handle := win32.HANDLE(fd) @@ -165,7 +165,7 @@ read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) { if is_console { total_read, err = read_console(handle, data[total_read:][:to_read]) - if err != 0 { + if err != nil { return total_read, err } } else { @@ -175,18 +175,18 @@ read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) { // Successful read can mean two things, including EOF, see: // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file if bytes_read == 0 { - return 0, ERROR_HANDLE_EOF + return 0, .EOF } else { - return int(bytes_read), ERROR_NONE + return int(bytes_read), nil } } else { - return 0, Errno(win32.GetLastError()) + return 0, get_last_error() } } - return total_read, ERROR_NONE + return total_read, nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { w: u32 switch whence { case 0: w = win32.FILE_BEGIN @@ -197,22 +197,23 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { lo := i32(offset) ft := win32.GetFileType(win32.HANDLE(fd)) if ft == win32.FILE_TYPE_PIPE { - return 0, ERROR_FILE_IS_PIPE + return 0, .File_Is_Pipe } dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) if dw_ptr == win32.INVALID_SET_FILE_POINTER { - err := Errno(win32.GetLastError()) + err := get_last_error() return 0, err } - return i64(hi)<<32 + i64(dw_ptr), ERROR_NONE + return i64(hi)<<32 + i64(dw_ptr), nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { length: win32.LARGE_INTEGER - err: Errno + err: Error if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { - err = Errno(win32.GetLastError()) + err = get_last_error() } return i64(length), err } @@ -220,10 +221,9 @@ file_size :: proc(fd: Handle) -> (i64, Errno) { @(private) MAX_RW :: 1<<30 -ERROR_EOF :: 38 @(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] @@ -239,15 +239,15 @@ pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { h := win32.HANDLE(fd) done: win32.DWORD - e: Errno + e: Error if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = Errno(win32.GetLastError()) + e = get_last_error() done = 0 } return int(done), e } @(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] @@ -261,9 +261,9 @@ pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { h := win32.HANDLE(fd) done: win32.DWORD - e: Errno + e: Error if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = Errno(win32.GetLastError()) + e = get_last_error() done = 0 } return int(done), e @@ -279,19 +279,19 @@ on Windows, read_at changes the position of the file cursor, on *nix, it does no will read from the location twice on *nix, and from two different locations on Windows */ -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { - return 0, ERROR_NEGATIVE_OFFSET + return 0, .Invalid_Offset } b, offset := data, offset for len(b) > 0 { m, e := pread(fd, b, offset) if e == ERROR_EOF { - err = 0 + err = nil break } - if e != 0 { + if e != nil { err = e break } @@ -311,18 +311,14 @@ on Windows, write_at changes the position of the file cursor, on *nix, it does n will write to the location twice on *nix, and to two different locations on Windows */ -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) { +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { - return 0, ERROR_NEGATIVE_OFFSET + return 0, .Invalid_Offset } b, offset := data, offset for len(b) > 0 { - m, e := pwrite(fd, b, offset) - if e != 0 { - err = e - break - } + m := pwrite(fd, b, offset) or_return n += m b = b[m:] offset += i64(m) @@ -338,6 +334,7 @@ stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE)) stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE)) +@(require_results) get_std_handle :: proc "contextless" (h: uint) -> Handle { fd := win32.GetStdHandle(win32.DWORD(h)) return Handle(fd) @@ -352,6 +349,7 @@ exists :: proc(path: string) -> bool { return attribs != win32.INVALID_FILE_ATTRIBUTES } +@(require_results) is_file :: proc(path: string) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() wpath := win32.utf8_to_wstring(path, context.temp_allocator) @@ -363,6 +361,7 @@ is_file :: proc(path: string) -> bool { return false } +@(require_results) is_dir :: proc(path: string) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() wpath := win32.utf8_to_wstring(path, context.temp_allocator) @@ -377,13 +376,14 @@ is_dir :: proc(path: string) -> bool { // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName @private cwd_lock := win32.SRWLOCK{} // zero is initialized +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { win32.AcquireSRWLockExclusive(&cwd_lock) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) sz_utf16 := win32.GetCurrentDirectoryW(0, nil) - dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. + dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. @@ -393,14 +393,14 @@ get_current_directory :: proc(allocator := context.allocator) -> string { return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else "" } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() wstr := win32.utf8_to_wstring(path, context.temp_allocator) win32.AcquireSRWLockExclusive(&cwd_lock) if !win32.SetCurrentDirectoryW(wstr) { - err = Errno(win32.GetLastError()) + err = get_last_error() } win32.ReleaseSRWLockExclusive(&cwd_lock) @@ -409,31 +409,31 @@ set_current_directory :: proc(path: string) -> (err: Errno) { } change_directory :: set_current_directory -make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) { +make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() // Mode is unused on Windows, but is needed on *nix wpath := win32.utf8_to_wstring(path, context.temp_allocator) if !win32.CreateDirectoryW(wpath, nil) { - err = Errno(win32.GetLastError()) + err = get_last_error() } return } -remove_directory :: proc(path: string) -> (err: Errno) { +remove_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() wpath := win32.utf8_to_wstring(path, context.temp_allocator) if !win32.RemoveDirectoryW(wpath) { - err = Errno(win32.GetLastError()) + err = get_last_error() } return } -@(private) +@(private, require_results) is_abs :: proc(path: string) -> bool { if len(path) > 0 && path[0] == '/' { return true @@ -449,7 +449,7 @@ is_abs :: proc(path: string) -> bool { return false } -@(private) +@(private, require_results) fix_long_path :: proc(path: string) -> string { if len(path) < 248 { return path @@ -464,7 +464,7 @@ fix_long_path :: proc(path: string) -> string { prefix :: `\\?` - path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) + path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) copy(path_buf, prefix) n := len(path) r, w := 0, len(prefix) @@ -494,80 +494,69 @@ fix_long_path :: proc(path: string) -> string { } -link :: proc(old_name, new_name: string) -> (err: Errno) { +link :: proc(old_name, new_name: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() n := win32.utf8_to_wstring(fix_long_path(new_name)) o := win32.utf8_to_wstring(fix_long_path(old_name)) - return Errno(win32.CreateHardLinkW(n, o, nil)) + return Platform_Error(win32.CreateHardLinkW(n, o, nil)) } -unlink :: proc(path: string) -> (err: Errno) { +unlink :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() wpath := win32.utf8_to_wstring(path, context.temp_allocator) if !win32.DeleteFileW(wpath) { - err = Errno(win32.GetLastError()) + err = get_last_error() } return } -rename :: proc(old_path, new_path: string) -> (err: Errno) { +rename :: proc(old_path, new_path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() from := win32.utf8_to_wstring(old_path, context.temp_allocator) to := win32.utf8_to_wstring(new_path, context.temp_allocator) if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { - err = Errno(win32.GetLastError()) + err = get_last_error() } return } -ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) { - curr_off, e := seek(fd, 0, 1) - if e != 0 { - return e - } +ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { + curr_off := seek(fd, 0, 1) or_return defer seek(fd, curr_off, 0) - _, e = seek(fd, length, 0) - if e != 0 { - return e - } + _= seek(fd, length, 0) or_return ok := win32.SetEndOfFile(win32.HANDLE(fd)) if !ok { - return Errno(win32.GetLastError()) + return get_last_error() } - return ERROR_NONE + return nil } -truncate :: proc(path: string, length: i64) -> (err: Errno) { - fd: Handle - fd, err = open(path, O_WRONLY|O_CREATE, 0o666) - if err != 0 { - return - } +truncate :: proc(path: string, length: i64) -> (err: Error) { + fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return defer close(fd) - err = ftruncate(fd, length) - return + return ftruncate(fd, length) } -remove :: proc(name: string) -> Errno { +remove :: proc(name: string) -> Error { p := win32.utf8_to_wstring(fix_long_path(name)) err, err1: win32.DWORD if !win32.DeleteFileW(p) { err = win32.GetLastError() } if err == 0 { - return 0 + return nil } if !win32.RemoveDirectoryW(p) { err1 = win32.GetLastError() } if err1 == 0 { - return 0 + return nil } if err != err1 { @@ -588,16 +577,17 @@ remove :: proc(name: string) -> Errno { } } - return Errno(err) + return Platform_Error(err) } -pipe :: proc() -> (r, w: Handle, err: Errno) { +@(require_results) +pipe :: proc() -> (r, w: Handle, err: Error) { sa: win32.SECURITY_ATTRIBUTES sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) sa.bInheritHandle = true if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) { - err = Errno(win32.GetLastError()) + err = get_last_error() } return } diff --git a/core/os/os.odin b/core/os/os.odin index 51652a52b..568c0a2aa 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -1,6 +1,8 @@ package os +import "base:intrinsics" import "base:runtime" +import "core:io" import "core:strconv" import "core:unicode/utf8" @@ -13,15 +15,15 @@ SEEK_SET :: 0 SEEK_CUR :: 1 SEEK_END :: 2 -write_string :: proc(fd: Handle, str: string) -> (int, Errno) { +write_string :: proc(fd: Handle, str: string) -> (int, Error) { return write(fd, transmute([]byte)str) } -write_byte :: proc(fd: Handle, b: byte) -> (int, Errno) { +write_byte :: proc(fd: Handle, b: byte) -> (int, Error) { return write(fd, []byte{b}) } -write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) { +write_rune :: proc(fd: Handle, r: rune) -> (int, Error) { if r < utf8.RUNE_SELF { return write_byte(fd, byte(r)) } @@ -30,105 +32,94 @@ write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) { return write(fd, b[:n]) } -write_encoded_rune :: proc(fd: Handle, r: rune) { - write_byte(fd, '\'') +write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) { + wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { + n^ += m + if merr != nil { + err^ = merr + return true + } + return false + } + + if wrap(write_byte(f, '\''), &n, &err) { return } switch r { - case '\a': write_string(fd, "\\a") - case '\b': write_string(fd, "\\b") - case '\e': write_string(fd, "\\e") - case '\f': write_string(fd, "\\f") - case '\n': write_string(fd, "\\n") - case '\r': write_string(fd, "\\r") - case '\t': write_string(fd, "\\t") - case '\v': write_string(fd, "\\v") + case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } + case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } + case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } + case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } + case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } + case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } + case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } + case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } case: if r < 32 { - write_string(fd, "\\x") + if wrap(write_string(f, "\\x"), &n, &err) { return } b: [2]byte s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { - case 0: write_string(fd, "00") - case 1: write_rune(fd, '0') - case 2: write_string(fd, s) + case 0: if wrap(write_string(f, "00"), &n, &err) { return } + case 1: if wrap(write_rune(f, '0'), &n, &err) { return } + case 2: if wrap(write_string(f, s), &n, &err) { return } } } else { - write_rune(fd, r) + if wrap(write_rune(f, r), &n, &err) { return } } } - write_byte(fd, '\'') + _ = wrap(write_byte(f, '\''), &n, &err) + return } -read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Errno) { +read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) { if len(buf) < min { - return 0, -1 + return 0, io.Error.Short_Buffer } nn := max(int) - for nn > 0 && n < min && err == 0 { + for nn > 0 && n < min && err == nil { nn, err = read(fd, buf[n:]) n += nn } if n >= min { - err = 0 + err = nil } return } -read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Errno) { +read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) { return read_at_least(fd, buf, len(buf)) } +@(require_results) file_size_from_path :: proc(path: string) -> i64 { fd, err := open(path, O_RDONLY, 0) - if err != 0 { + if err != nil { return -1 } defer close(fd) length: i64 - if length, err = file_size(fd); err != 0 { + if length, err = file_size(fd); err != nil { return -1 } return length } +@(require_results) read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { - context.allocator = allocator - - fd, err := open(name, O_RDONLY, 0) - if err != 0 { - return nil, false - } - defer close(fd) - - return read_entire_file_from_handle(fd, allocator, loc) + err: Error + data, err = read_entire_file_from_filename_or_err(name, allocator, loc) + success = err == nil + return } +@(require_results) read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { - context.allocator = allocator - - length: i64 - err: Errno - if length, err = file_size(fd); err != 0 { - return nil, false - } - - if length <= 0 { - return nil, true - } - - data = make([]byte, int(length), allocator, loc) - if data == nil { - return nil, false - } - - bytes_read, read_err := read_full(fd, data) - if read_err != ERROR_NONE { - delete(data) - return nil, false - } - return data[:bytes_read], true + err: Error + data, err = read_entire_file_from_handle_or_err(fd, allocator, loc) + success = err == nil + return } read_entire_file :: proc { @@ -136,7 +127,50 @@ read_entire_file :: proc { read_entire_file_from_handle, } +@(require_results) +read_entire_file_from_filename_or_err :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { + context.allocator = allocator + + fd := open(name, O_RDONLY, 0) or_return + defer close(fd) + + return read_entire_file_from_handle_or_err(fd, allocator, loc) +} + +@(require_results) +read_entire_file_from_handle_or_err :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { + context.allocator = allocator + + length := file_size(fd) or_return + if length <= 0 { + return nil, nil + } + + data = make([]byte, int(length), allocator, loc) or_return + if data == nil { + return nil, nil + } + defer if err != nil { + delete(data, allocator) + } + + bytes_read := read_full(fd, data) or_return + data = data[:bytes_read] + return +} + +read_entire_file_or_err :: proc { + read_entire_file_from_filename_or_err, + read_entire_file_from_handle_or_err, +} + + write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (success: bool) { + return write_entire_file_or_err(name, data, truncate) == nil +} + +@(require_results) +write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) -> Error { flags: int = O_WRONLY|O_CREATE if truncate { flags |= O_TRUNC @@ -148,21 +182,18 @@ write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (succ mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH } - fd, err := open(name, flags, mode) - if err != 0 { - return false - } + fd := open(name, flags, mode) or_return defer close(fd) - _, write_err := write(fd, data) - return write_err == 0 + _ = write(fd, data) or_return + return nil } -write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) { +write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { return write(fd, ([^]byte)(data)[:len]) } -read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) { +read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { return read(fd, ([^]byte)(data)[:len]) } @@ -173,6 +204,7 @@ heap_alloc :: runtime.heap_alloc heap_resize :: runtime.heap_resize heap_free :: runtime.heap_free +@(require_results) processor_core_count :: proc() -> int { return _processor_core_count() } diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index 40672face..ef73809b1 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -10,20 +10,29 @@ file_allocator :: proc() -> runtime.Allocator { temp_allocator_proc :: runtime.arena_allocator_proc +@(private="file") +MAX_TEMP_ARENA_COUNT :: 2 + @(private="file", thread_local) -global_default_temp_allocator_arena: runtime.Arena +global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena + +@(private="file", thread_local) +global_default_temp_allocator_index: uint + @(require_results) temp_allocator :: proc() -> runtime.Allocator { return runtime.Allocator{ procedure = temp_allocator_proc, - data = &global_default_temp_allocator_arena, + data = &global_default_temp_allocator_arenas[global_default_temp_allocator_index], } } + + @(require_results) temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { - temp = runtime.arena_temp_begin(&global_default_temp_allocator_arena, loc) + temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc) return } @@ -33,16 +42,23 @@ temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_locatio @(fini, private) temp_allocator_fini :: proc() { - runtime.arena_destroy(&global_default_temp_allocator_arena) - global_default_temp_allocator_arena = {} + for &arena in global_default_temp_allocator_arenas { + runtime.arena_destroy(&arena) + } + global_default_temp_allocator_arenas = {} } -@(deferred_out=temp_allocator_temp_end) -TEMP_ALLOCATOR_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { - if ignore { - return {}, loc - } else { - return temp_allocator_temp_begin(loc), loc +TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { + runtime.arena_temp_end(temp, loc) + if temp.arena != nil { + global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT } } +@(deferred_out=TEMP_ALLOCATOR_GUARD_END) +TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { + tmp := temp_allocator_temp_begin(loc) + global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT + return tmp, loc +} + diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin new file mode 100644 index 000000000..a41ef68f9 --- /dev/null +++ b/core/os/os2/dir.odin @@ -0,0 +1,82 @@ +package os2 + +import "base:runtime" +import "core:slice" + +read_dir :: read_directory + +@(require_results) +read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { + if f == nil { + return nil, .Invalid_File + } + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + TEMP_ALLOCATOR_GUARD() + + it := read_directory_iterator_create(f) or_return + defer _read_directory_iterator_destroy(&it) + + dfi := make([dynamic]File_Info, 0, size, temp_allocator()) + defer if err != nil { + for fi in dfi { + file_info_delete(fi, allocator) + } + } + + for fi, index in read_directory_iterator(&it) { + if n > 0 && index == n { + break + } + append(&dfi, file_info_clone(fi, allocator) or_return) + } + + return slice.clone(dfi[:], allocator) +} + + +@(require_results) +read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory(f, -1, allocator) +} + +@(require_results) +read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + f := open(path) or_return + defer close(f) + return read_directory(f, n, allocator) +} + +@(require_results) +read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory_by_path(path, -1, allocator) +} + + + +Read_Directory_Iterator :: struct { + f: ^File, + impl: Read_Directory_Iterator_Impl, +} + + +@(require_results) +read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { + return _read_directory_iterator_create(f) +} + +read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + _read_directory_iterator_destroy(it) +} + +// NOTE(bill): `File_Info` does not need to deleted on each iteration. Any copies must be manually copied with `file_info_clone` +@(require_results) +read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + return _read_directory_iterator(it) +} diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin new file mode 100644 index 000000000..d4f62e213 --- /dev/null +++ b/core/os/os2/dir_linux.odin @@ -0,0 +1,20 @@ +//+private +package os2 + +Read_Directory_Iterator_Impl :: struct { + +} + + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + return +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { + return {}, nil +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { +} diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin new file mode 100644 index 000000000..84f320095 --- /dev/null +++ b/core/os/os2/dir_windows.odin @@ -0,0 +1,141 @@ +//+private +package os2 + +import "base:runtime" +import "core:time" +import win32 "core:sys/windows" + +@(private="file") +find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + // Ignore "." and ".." + if d.cFileName[0] == '.' && d.cFileName[1] == 0 { + return + } + if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { + return + } + path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return + + + fi.fullpath = path + fi.name = basename(path) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0) + + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + + + handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) + defer win32.CloseHandle(handle) + + if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) { + #assert(size_of(fi.inode) == size_of(file_id_info.FileId)) + #assert(size_of(fi.inode) == 16) + runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16) + } + + + return +} + +Read_Directory_Iterator_Impl :: struct { + find_data: win32.WIN32_FIND_DATAW, + find_handle: win32.HANDLE, + path: string, + prev_fi: File_Info, + no_more_files: bool, + index: int, +} + + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + if it.f == nil { + return + } + + TEMP_ALLOCATOR_GUARD() + + for !it.impl.no_more_files { + err: Error + file_info_delete(it.impl.prev_fi, file_allocator()) + it.impl.prev_fi = {} + + fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator()) + if err != nil { + return + } + if fi.name != "" { + it.impl.prev_fi = fi + ok = true + index = it.impl.index + it.impl.index += 1 + } + + if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) { + e := _get_platform_error() + if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) { + it.impl.no_more_files = true + } + it.impl.no_more_files = true + } + if ok { + return + } + } + return +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) { + if f == nil { + return + } + it.f = f + impl := (^File_Impl)(f.impl) + + if !is_directory(impl.name) { + err = .Invalid_Dir + return + } + + wpath: []u16 + { + i := 0 + for impl.wname[i] != 0 { + i += 1 + } + wpath = impl.wname[:i] + } + + TEMP_ALLOCATOR_GUARD() + + wpath_search := make([]u16, len(wpath)+3, temp_allocator()) + copy(wpath_search, wpath) + wpath_search[len(wpath)+0] = '\\' + wpath_search[len(wpath)+1] = '*' + wpath_search[len(wpath)+2] = 0 + + it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data) + if it.impl.find_handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + defer if err != nil { + win32.FindClose(it.impl.find_handle) + } + + it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it.f == nil { + return + } + file_info_delete(it.impl.prev_fi, file_allocator()) + win32.FindClose(it.impl.find_handle) +} \ No newline at end of file diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 39694b821..870b5a731 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -8,7 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string if key == "" { return } - wkey := win32.utf8_to_wstring(key) + TEMP_ALLOCATOR_GUARD() + wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) n := win32.GetEnvironmentVariableW(wkey, nil, 0) if n == 0 { @@ -32,20 +33,22 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", false } - value = win32.utf16_to_utf8(b[:n], allocator) or_else "" + value = win32_utf16_to_utf8(b[:n], allocator) or_else "" found = true return } _set_env :: proc(key, value: string) -> bool { - k := win32.utf8_to_wstring(key) - v := win32.utf8_to_wstring(value) + TEMP_ALLOCATOR_GUARD() + k, _ := win32_utf8_to_wstring(key, temp_allocator()) + v, _ := win32_utf8_to_wstring(value, temp_allocator()) return bool(win32.SetEnvironmentVariableW(k, v)) } _unset_env :: proc(key: string) -> bool { - k := win32.utf8_to_wstring(key) + TEMP_ALLOCATOR_GUARD() + k, _ := win32_utf8_to_wstring(key, temp_allocator()) return bool(win32.SetEnvironmentVariableW(k, nil)) } @@ -89,7 +92,7 @@ _environ :: proc(allocator: runtime.Allocator) -> []string { break } w := ([^]u16)(p)[from:i] - append(&r, win32.utf16_to_utf8(w, allocator) or_else "") + append(&r, win32_utf16_to_utf8(w, allocator) or_else "") from = i + 1 } } diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 51d8314b4..bc51bb1e8 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -22,6 +22,7 @@ General_Error :: enum u32 { Invalid_File, Invalid_Dir, Invalid_Path, + Invalid_Callback, Pattern_Has_Separator, @@ -38,14 +39,17 @@ Error :: union #shared_nil { } #assert(size_of(Error) == size_of(u64)) +ERROR_NONE :: Error{} +@(require_results) is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) { v := ferr.(Platform_Error) or_else {} return i32(v), i32(v) != 0 } +@(require_results) error_string :: proc(ferr: Error) -> string { if ferr == nil { return "" @@ -64,6 +68,7 @@ error_string :: proc(ferr: Error) -> string { case .Invalid_File: return "invalid file" case .Invalid_Dir: return "invalid directory" case .Invalid_Path: return "invalid path" + case .Invalid_Callback: return "invalid callback" case .Unsupported: return "unsupported" case .Pattern_Has_Separator: return "pattern has separator" } diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index d7234ce8b..7f28d1c41 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -4,8 +4,8 @@ package os2 import "core:sys/linux" @(rodata) -_errno_strings : [linux.Errno]string = { - .NONE = "Success", +_errno_strings := [linux.Errno]string{ + .NONE = "", .EPERM = "Operation not permitted", .ENOENT = "No such file or directory", .ESRCH = "No such process", diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6500e7ccc..6421d26ee 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -1,6 +1,8 @@ //+private package os2 +import "base:runtime" +import "core:slice" import win32 "core:sys/windows" _error_string :: proc(errno: i32) -> string { @@ -8,9 +10,14 @@ _error_string :: proc(errno: i32) -> string { if e == 0 { return "" } - // TODO(bill): _error_string for windows - // FormatMessageW - return "" + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + return "" } _get_platform_error :: proc() -> Error { diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 236423163..454bc50b9 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -4,20 +4,57 @@ import "core:io" import "core:time" import "base:runtime" +/* + Type representing a file handle. + + This struct represents an OS-specific file-handle, which can be one of + the following: + - File + - Directory + - Pipe + - Named pipe + - Block Device + - Character device + - Symlink + - Socket + + See `File_Type` enum for more information on file types. +*/ File :: struct { - impl: _File, + impl: rawptr, stream: io.Stream, - user_fstat: Fstat_Callback, + fstat: Fstat_Callback, } -File_Mode :: distinct u32 -File_Mode_Dir :: File_Mode(1<<16) -File_Mode_Named_Pipe :: File_Mode(1<<17) -File_Mode_Device :: File_Mode(1<<18) -File_Mode_Char_Device :: File_Mode(1<<19) -File_Mode_Sym_Link :: File_Mode(1<<20) +/* + Type representing the type of a file handle. -File_Mode_Perm :: File_Mode(0o777) // Unix permision bits + **Note(windows)**: Socket handles can not be distinguished from + files, as they are just a normal file handle that is being treated by + a special driver. Windows also makes no distinction between block and + character devices. +*/ +File_Type :: enum { + // The type of a file could not be determined for the current platform. + Undetermined, + // Represents a regular file. + Regular, + // Represents a directory. + Directory, + // Represents a symbolic link. + Symlink, + // Represents a named pipe (FIFO). + Named_Pipe, + // Represents a socket. + // **Note(windows)**: Not returned on windows + Socket, + // Represents a block device. + // **Note(windows)**: On windows represents all devices. + Block_Device, + // Represents a character device. + // **Note(windows)**: Not returned on windows + Character_Device, +} File_Flags :: distinct bit_set[File_Flag; uint] File_Flag :: enum { @@ -29,7 +66,7 @@ File_Flag :: enum { Sync, Trunc, Sparse, - Close_On_Exec, + Inheritable, Unbuffered_IO, } @@ -43,7 +80,15 @@ O_EXCL :: File_Flags{.Excl} O_SYNC :: File_Flags{.Sync} O_TRUNC :: File_Flags{.Trunc} O_SPARSE :: File_Flags{.Sparse} -O_CLOEXEC :: File_Flags{.Close_On_Exec} + +/* + If specified, the file handle is inherited upon the creation of a child + process. By default all handles are created non-inheritable. + + **Note**: The standard file handles (stderr, stdout and stdin) are always + initialized as inheritable. +*/ +O_INHERITABLE :: File_Flags{.Inheritable} stdin: ^File = nil // OS-Specific stdout: ^File = nil // OS-Specific @@ -51,17 +96,26 @@ stderr: ^File = nil // OS-Specific @(require_results) create :: proc(name: string) -> (^File, Error) { - return open(name, {.Read, .Write, .Create}, File_Mode(0o777)) + return open(name, {.Read, .Write, .Create}, 0o777) } @(require_results) -open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) { +open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { return _open(name, flags, perm) } +// @(require_results) +// open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { +// if buffer_size == 0 { +// return _open(name, flags, perm) +// } +// return _open_buffered(name, buffer_size, flags, perm) +// } + + @(require_results) new_file :: proc(handle: uintptr, name: string) -> ^File { - return _new_file(handle, name) + return _new_file(handle, name) or_else panic("Out of memory") } @(require_results) @@ -161,44 +215,56 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) chdir :: change_directory + change_directory :: proc(name: string) -> Error { return _chdir(name) } chmod :: change_mode -change_mode :: proc(name: string, mode: File_Mode) -> Error { + +change_mode :: proc(name: string, mode: int) -> Error { return _chmod(name, mode) } + chown :: change_owner + change_owner :: proc(name: string, uid, gid: int) -> Error { return _chown(name, uid, gid) } fchdir :: fchange_directory + fchange_directory :: proc(f: ^File) -> Error { return _fchdir(f) } + fchmod :: fchange_mode -fchange_mode :: proc(f: ^File, mode: File_Mode) -> Error { + +fchange_mode :: proc(f: ^File, mode: int) -> Error { return _fchmod(f, mode) } fchown :: fchange_owner + fchange_owner :: proc(f: ^File, uid, gid: int) -> Error { return _fchown(f, uid, gid) } lchown :: change_owner_do_not_follow_links + change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error { return _lchown(name, uid, gid) } chtimes :: change_times + change_times :: proc(name: string, atime, mtime: time.Time) -> Error { return _chtimes(name, atime, mtime) } + fchtimes :: fchange_times + fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error { return _fchtimes(f, atime, mtime) } @@ -210,13 +276,24 @@ exists :: proc(path: string) -> bool { @(require_results) is_file :: proc(path: string) -> bool { - return _is_file(path) + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + if err != nil { + return false + } + return fi.type == .Regular } is_dir :: is_directory + @(require_results) is_directory :: proc(path: string) -> bool { - return _is_dir(path) + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + if err != nil { + return false + } + return fi.type == .Directory } @@ -226,11 +303,11 @@ copy_file :: proc(dst_path, src_path: string) -> Error { info := fstat(src, file_allocator()) or_return defer file_info_delete(info, file_allocator()) - if info.is_directory { + if info.type == .Directory { return .Invalid_File } - dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return + dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & 0o777) or_return defer close(dst) _, err := io.copy(to_writer(dst), to_reader(src)) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 8e7db9751..6b981cca1 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -1,46 +1,55 @@ //+private package os2 +import "base:runtime" import "core:io" import "core:time" -import "base:runtime" +import "core:sync" import "core:sys/linux" -_File :: struct { +File_Impl :: struct { + file: File, name: string, fd: linux.Fd, allocator: runtime.Allocator, + + buffer: []byte, + rw_mutex: sync.RW_Mutex, // read write calls + p_mutex: sync.Mutex, // pread pwrite calls } -_stdin : File = { - impl = { +_stdin := File{ + impl = &File_Impl{ name = "/proc/self/fd/0", fd = 0, - allocator = _file_allocator(), + allocator = file_allocator(), }, stream = { procedure = _file_stream_proc, }, + fstat = _fstat, } -_stdout : File = { - impl = { +_stdout := File{ + impl = &File_Impl{ name = "/proc/self/fd/1", fd = 1, - allocator = _file_allocator(), + allocator = file_allocator(), }, stream = { procedure = _file_stream_proc, }, + fstat = _fstat, } -_stderr : File = { - impl = { +_stderr := File{ + impl = &File_Impl{ name = "/proc/self/fd/2", fd = 2, - allocator = _file_allocator(), + allocator = file_allocator(), }, stream = { procedure = _file_stream_proc, }, + fstat = _fstat, } @init @@ -55,74 +64,84 @@ _standard_stream_init :: proc() { stderr = &_stderr } -_file_allocator :: proc() -> runtime.Allocator { - return heap_allocator() -} - -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return // Just default to using O_NOCTTY because needing to open a controlling // terminal would be incredibly rare. This has no effect on files while // allowing us to open serial devices. - sys_flags: linux.Open_Flags = {.NOCTTY} + sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC} switch flags & O_RDONLY|O_WRONLY|O_RDWR { case O_RDONLY: case O_WRONLY: sys_flags += {.WRONLY} case O_RDWR: sys_flags += {.RDWR} } - if .Append in flags { sys_flags += {.APPEND} } if .Create in flags { sys_flags += {.CREAT} } if .Excl in flags { sys_flags += {.EXCL} } if .Sync in flags { sys_flags += {.DSYNC} } if .Trunc in flags { sys_flags += {.TRUNC} } - if .Close_On_Exec in flags { sys_flags += {.CLOEXEC} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } - fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm))) + fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm)) if errno != .NONE { return nil, _get_platform_error(errno) } - return _new_file(uintptr(fd), name), nil + return _new_file(uintptr(fd), name) } -_new_file :: proc(fd: uintptr, _: string = "") -> ^File { - file := new(File, file_allocator()) - _construct_file(file, fd, "") - return file -} - -_construct_file :: proc(file: ^File, fd: uintptr, _: string = "") { - file^ = { - impl = { - fd = linux.Fd(fd), - allocator = file_allocator(), - name = _get_full_path(file.impl.fd, file.impl.allocator), - }, - stream = { - data = file, - procedure = _file_stream_proc, - }, +_new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) { + impl := new(File_Impl, file_allocator()) or_return + defer if err != nil { + free(impl, file_allocator()) } + impl.file.impl = impl + impl.fd = linux.Fd(fd) + impl.allocator = file_allocator() + impl.name = _get_full_path(impl.fd, file_allocator()) or_return + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file, nil } -_destroy :: proc(f: ^File) -> Error { + +@(require_results) +_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) { + assert(buffer_size > 0) + f, err = _open(name, flags, perm) + if f != nil && err == nil { + impl := (^File_Impl)(f.impl) + impl.buffer = make([]byte, buffer_size, file_allocator()) + f.stream.procedure = _file_stream_buffered_proc + } + return +} + +_destroy :: proc(f: ^File_Impl) -> Error { if f == nil { return nil } - delete(f.impl.name, f.impl.allocator) - free(f, f.impl.allocator) + a := f.allocator + err0 := delete(f.name, a) + err1 := delete(f.buffer, a) + err2 := free(f, a) + err0 or_return + err1 or_return + err2 or_return return nil } -_close :: proc(f: ^File) -> Error { - if f == nil { +_close :: proc(f: ^File_Impl) -> Error { + if f == nil{ return nil } - errno := linux.close(f.impl.fd) + errno := linux.close(f.fd) if errno == .EBADF { // avoid possible double free return _get_platform_error(errno) } @@ -131,41 +150,41 @@ _close :: proc(f: ^File) -> Error { } _fd :: proc(f: ^File) -> uintptr { - if f == nil { + if f == nil || f.impl == nil { return ~uintptr(0) } - return uintptr(f.impl.fd) + impl := (^File_Impl)(f.impl) + return uintptr(impl.fd) } _name :: proc(f: ^File) -> string { - return f.impl.name if f != nil else "" + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence)) +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) if errno != .NONE { return -1, _get_platform_error(errno) } return n, nil } -_read :: proc(f: ^File, p: []byte) -> (i64, Error) { +_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n, errno := linux.read(f.impl.fd, p[:]) + n, errno := linux.read(f.fd, p[:]) if errno != .NONE { return -1, _get_platform_error(errno) } - return i64(n), n == 0 ? io.Error.EOF : nil + return i64(n), io.Error.EOF if n == 0 else nil } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { if offset < 0 { return 0, .Invalid_Offset } - - n, errno := linux.pread(f.impl.fd, p[:], offset) + n, errno := linux.pread(f.fd, p[:], offset) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -175,32 +194,31 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { return i64(n), nil } -_write :: proc(f: ^File, p: []byte) -> (i64, Error) { +_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n, errno := linux.write(f.impl.fd, p[:]) + n, errno := linux.write(f.fd, p[:]) if errno != .NONE { return -1, _get_platform_error(errno) } return i64(n), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { if offset < 0 { return 0, .Invalid_Offset } - - n, errno := linux.pwrite(f.impl.fd, p[:], offset) + n, errno := linux.pwrite(f.fd, p[:], offset) if errno != .NONE { return -1, _get_platform_error(errno) } return i64(n), nil } -_file_size :: proc(f: ^File) -> (n: i64, err: Error) { +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { s: linux.Stat = --- - errno := linux.fstat(f.impl.fd, &s) + errno := linux.fstat(f.fd, &s) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -208,27 +226,38 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { } _sync :: proc(f: ^File) -> Error { - return _get_platform_error(linux.fsync(f.impl.fd)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fsync(impl.fd)) } -_flush :: proc(f: ^File) -> Error { - return _get_platform_error(linux.fsync(f.impl.fd)) +_flush :: proc(f: ^File_Impl) -> Error { + return _get_platform_error(linux.fsync(f.fd)) } _truncate :: proc(f: ^File, size: i64) -> Error { - return _get_platform_error(linux.ftruncate(f.impl.fd, size)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.ftruncate(impl.fd, size)) } _remove :: proc(name: string) -> Error { + is_dir_fd :: proc(fd: linux.Fd) -> bool { + s: linux.Stat + if linux.fstat(fd, &s) != .NONE { + return false + } + return linux.S_ISDIR(s.mode) + } + TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return fd, errno := linux.open(name_cstr, {.NOFOLLOW}) #partial switch (errno) { - case .ELOOP: /* symlink */ + case .ELOOP: + /* symlink */ case .NONE: defer linux.close(fd) - if _is_dir_fd(fd) { + if is_dir_fd(fd) { return _get_platform_error(linux.rmdir(name_cstr)) } case: @@ -292,17 +321,19 @@ _chdir :: proc(name: string) -> Error { } _fchdir :: proc(f: ^File) -> Error { - return _get_platform_error(linux.fchdir(f.impl.fd)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchdir(impl.fd)) } -_chmod :: proc(name: string, mode: File_Mode) -> Error { +_chmod :: proc(name: string, mode: int) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) } -_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - return _get_platform_error(linux.fchmod(f.impl.fd, transmute(linux.Mode)(u32(mode)))) +_fchmod :: proc(f: ^File, mode: int) -> Error { + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode)))) } // NOTE: will throw error without super user priviledges @@ -321,7 +352,8 @@ _lchown :: proc(name: string, uid, gid: int) -> Error { // NOTE: will throw error without super user priviledges _fchown :: proc(f: ^File, uid, gid: int) -> Error { - return _get_platform_error(linux.fchown(f.impl.fd, linux.Uid(uid), linux.Gid(gid))) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid))) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { @@ -351,7 +383,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { uint(mtime._nsec) % uint(time.Second), }, } - return _get_platform_error(linux.utimensat(f.impl.fd, nil, ×[0], nil)) + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.utimensat(impl.fd, nil, ×[0], nil)) } _exists :: proc(name: string) -> bool { @@ -361,42 +394,6 @@ _exists :: proc(name: string) -> bool { return !res && errno == .NONE } -_is_file :: proc(name: string) -> bool { - TEMP_ALLOCATOR_GUARD() - name_cstr, _ := temp_cstring(name) - s: linux.Stat - if linux.stat(name_cstr, &s) != .NONE { - return false - } - return linux.S_ISREG(s.mode) -} - -_is_file_fd :: proc(fd: linux.Fd) -> bool { - s: linux.Stat - if linux.fstat(fd, &s) != .NONE { - return false - } - return linux.S_ISREG(s.mode) -} - -_is_dir :: proc(name: string) -> bool { - TEMP_ALLOCATOR_GUARD() - name_cstr, _ := temp_cstring(name) - s: linux.Stat - if linux.stat(name_cstr, &s) != .NONE { - return false - } - return linux.S_ISDIR(s.mode) -} - -_is_dir_fd :: proc(fd: linux.Fd) -> bool { - s: linux.Stat - if linux.fstat(fd, &s) != .NONE { - return false - } - return linux.S_ISDIR(s.mode) -} - /* Certain files in the Linux file system are not actual * files (e.g. everything in /proc/). Therefore, the * read_entire_file procs fail to actually read anything @@ -443,7 +440,51 @@ _read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Alloc @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) + ferr: Error + switch mode { + case .Read: + n, ferr = _read(f, p) + err = error_to_io_error(ferr) + return + case .Read_At: + n, ferr = _read_at(f, p, offset) + err = error_to_io_error(ferr) + return + case .Write: + n, ferr = _write(f, p) + err = error_to_io_error(ferr) + return + case .Write_At: + n, ferr = _write_at(f, p, offset) + err = error_to_io_error(ferr) + return + case .Seek: + n, ferr = _seek(f, offset, whence) + err = error_to_io_error(ferr) + return + case .Size: + n, ferr = _file_size(f) + err = error_to_io_error(ferr) + return + case .Flush: + ferr = _flush(f) + err = error_to_io_error(ferr) + return + case .Close, .Destroy: + ferr = _close(f) + err = error_to_io_error(ferr) + return + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + } + return 0, .Empty +} + + +@(private="package") +_file_stream_buffered_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File_Impl)(stream_data) ferr: Error switch mode { case .Read: diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 977979bae..b982afb3e 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -8,6 +8,18 @@ write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { return write(f, transmute([]byte)s) } +write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) { + for s in strings { + m: int + m, err = write_string(f, s) + n += m + if err != nil { + return + } + } + return +} + write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { return write(f, []byte{b}) } @@ -61,6 +73,24 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { return } +read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) { + if len(buf) < min { + return 0, .Short_Buffer + } + nn := max(int) + for nn > 0 && n < min && err == nil { + nn, err = read(f, buf[n:]) + n += nn + } + if n >= min { + err = nil + } + return +} + +read_full :: proc(f: ^File, buf: []byte) -> (n: int, err: Error) { + return read_at_least(f, buf, len(buf)) +} write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { return write(f, ([^]byte)(data)[:len]) @@ -138,16 +168,13 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d } @(require_results) -write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { +write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := true) -> Error { flags := O_WRONLY|O_CREATE if truncate { flags |= O_TRUNC } - f, err := open(name, flags, perm) - if err != nil { - return err - } - _, err = write(f, data) + f := open(name, flags, perm) or_return + _, err := write(f, data) if cerr := close(f); cerr != nil && err == nil { err = cerr } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 37f8f44de..74067464b 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -17,20 +17,27 @@ _ERROR_BAD_NETPATH :: 53 MAX_RW :: 1<<30 -_File_Kind :: enum u8 { +File_Impl_Kind :: enum u8 { File, Console, Pipe, } -_File :: struct { +File_Impl :: struct { + file: File, + fd: rawptr, name: string, wname: win32.wstring, - kind: _File_Kind, + kind: File_Impl_Kind, allocator: runtime.Allocator, + r_buf: []byte, + w_buf: []byte, + w_n: int, + max_consecutive_empty_writes: int, + rw_mutex: sync.RW_Mutex, // read write calls p_mutex: sync.Mutex, // pread pwrite calls } @@ -53,13 +60,14 @@ _handle :: proc(f: ^File) -> win32.HANDLE { return win32.HANDLE(_fd(f)) } -_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) { +_open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: uintptr, err: Error) { if len(name) == 0 { err = .Not_Exist return } + TEMP_ALLOCATOR_GUARD() - path := _fix_long_path(name) + path := _fix_long_path(name, temp_allocator()) or_return access: u32 switch flags & {.Read, .Write} { case {.Read}: access = win32.FILE_GENERIC_READ @@ -75,11 +83,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han access |= win32.FILE_APPEND_DATA } share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) - sa: ^win32.SECURITY_ATTRIBUTES - if .Close_On_Exec not_in flags { - sa = &win32.SECURITY_ATTRIBUTES{} - sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) - sa.bInheritHandle = true + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = .Inheritable in flags, } create_mode: u32 = win32.OPEN_EXISTING @@ -94,14 +100,14 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han create_mode = win32.TRUNCATE_EXISTING } - attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL + attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS if perm & S_IWRITE == 0 { attrs = win32.FILE_ATTRIBUTE_READONLY if create_mode == win32.CREATE_ALWAYS { // NOTE(bill): Open has just asked to create a file in read-only mode. // If the file already exists, to make it akin to a *nix open call, // the call preserves the existing permissions. - h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) + h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) if h == win32.INVALID_HANDLE { switch e := win32.GetLastError(); e { case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: @@ -109,12 +115,13 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han case 0: return uintptr(h), nil case: - return 0, Platform_Error(e) + return 0, _get_platform_error() } } } } - h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil) + + h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) if h == win32.INVALID_HANDLE { return 0, _get_platform_error() } @@ -122,85 +129,119 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { flags := flags if flags != nil else {.Read} - handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return - return _new_file(handle, name), nil + handle := _open_internal(name, flags, perm) or_return + return _new_file(handle, name) } -_new_file :: proc(handle: uintptr, name: string) -> ^File { +_new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) { if handle == INVALID_HANDLE { - return nil + return + } + impl := new(File_Impl, file_allocator()) or_return + defer if err != nil { + free(impl, file_allocator()) } - f := new(File, file_allocator()) - f.impl.allocator = file_allocator() - f.impl.fd = rawptr(handle) - f.impl.name, _ = clone_string(name, f.impl.allocator) - f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator) + impl.file.impl = impl - handle := _handle(f) - kind := _File_Kind.File + impl.allocator = file_allocator() + impl.fd = rawptr(handle) + impl.name = clone_string(name, impl.allocator) or_return + impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return + + handle := _handle(&impl.file) + kind := File_Impl_Kind.File if m: u32; win32.GetConsoleMode(handle, &m) { kind = .Console } if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { kind = .Pipe } - f.impl.kind = kind + impl.kind = kind - f.stream = { - data = f, + impl.file.stream = { + data = impl, procedure = _file_stream_proc, } + impl.file.fstat = _fstat - return f + return &impl.file, nil } + +@(require_results) +_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) { + assert(buffer_size > 0) + flags := flags if flags != nil else {.Read} + handle := _open_internal(name, flags, perm) or_return + return _new_file_buffered(handle, name, buffer_size) +} + +_new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) -> (f: ^File, err: Error) { + f, err = _new_file(handle, name) + if f != nil && err == nil { + impl := (^File_Impl)(f.impl) + impl.r_buf = make([]byte, buffer_size, file_allocator()) + impl.w_buf = make([]byte, buffer_size, file_allocator()) + } + return +} + + _fd :: proc(f: ^File) -> uintptr { - if f == nil { + if f == nil || f.impl == nil { return INVALID_HANDLE } - return uintptr(f.impl.fd) + return uintptr((^File_Impl)(f.impl).fd) } -_destroy :: proc(f: ^File) -> Error { +_destroy :: proc(f: ^File_Impl) -> Error { if f == nil { return nil } - a := f.impl.allocator - free(f.impl.wname, a) - delete(f.impl.name, a) - free(f, a) + a := f.allocator + err0 := free(f.wname, a) + err1 := delete(f.name, a) + err2 := free(f, a) + err3 := delete(f.r_buf, a) + err4 := delete(f.w_buf, a) + err0 or_return + err1 or_return + err2 or_return + err3 or_return + err4 or_return return nil } -_close :: proc(f: ^File) -> Error { - if f == nil { +_close :: proc(f: ^File_Impl) -> Error { + if f == nil { return nil } - if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) { + if !win32.CloseHandle(win32.HANDLE(f.fd)) { return .Closed } return _destroy(f) } _name :: proc(f: ^File) -> string { - return f.impl.name if f != nil else "" + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - handle := _handle(f) +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + handle := _handle(&f.file) if handle == win32.INVALID_HANDLE { return 0, .Invalid_File } - if f.impl.kind == .Pipe { + + if f.kind == .Pipe { return 0, .Invalid_File } - sync.guard(&f.impl.rw_mutex) + sync.guard(&f.rw_mutex) w: u32 switch whence { @@ -218,13 +259,17 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er return i64(hi)<<32 + i64(dw_ptr), nil } -_read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + return _read_internal(f, p) +} + +_read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil } - // TODO(bill): should this be moved to `_File` instead? + // TODO(bill): should this be moved to `File_Impl` instead? BUF_SIZE :: 386 buf16: [BUF_SIZE]u16 buf8: [4*BUF_SIZE]u8 @@ -269,18 +314,18 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return } - handle := _handle(f) + handle := _handle(&f.file) single_read_length: win32.DWORD total_read: int length := len(p) - sync.shared_guard(&f.impl.rw_mutex) // multiple readers + sync.shared_guard(&f.rw_mutex) // multiple readers - if sync.guard(&f.impl.p_mutex) { + if sync.guard(&f.p_mutex) { to_read := min(win32.DWORD(length), MAX_RW) ok: win32.BOOL - if f.impl.kind == .Console { + if f.kind == .Console { n, cerr := read_console(handle, p[total_read:][:to_read]) total_read += n if cerr != nil { @@ -300,15 +345,15 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return i64(total_read), err } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pread :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] } - curr_offset := seek(f, offset, .Current) or_return - defer seek(f, curr_offset, .Start) + curr_offset := _seek(f, offset, .Current) or_return + defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ OffsetHigh = u32(offset>>32), @@ -317,7 +362,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { // TODO(bill): Determine the correct behaviour for consoles - h := _handle(f) + h := _handle(&f.file) done: win32.DWORD if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { err = _get_platform_error() @@ -327,7 +372,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - sync.guard(&f.impl.p_mutex) + sync.guard(&f.p_mutex) p, offset := p, offset for len(p) > 0 { @@ -339,7 +384,10 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } -_write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + return _write_internal(f, p) +} +_write_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { if len(p) == 0 { return } @@ -348,9 +396,9 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { total_write: i64 length := i64(len(p)) - handle := _handle(f) + handle := _handle(&f.file) - sync.guard(&f.impl.rw_mutex) + sync.guard(&f.rw_mutex) for total_write < length { remaining := length - total_write to_write := win32.DWORD(min(i32(remaining), MAX_RW)) @@ -366,22 +414,22 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return i64(total_write), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pwrite :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] } - curr_offset := seek(f, offset, .Current) or_return - defer seek(f, curr_offset, .Start) + curr_offset := _seek(f, offset, .Current) or_return + defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ OffsetHigh = u32(offset>>32), Offset = u32(offset), } - h := _handle(f) + h := _handle(&f.file) done: win32.DWORD if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { err = _get_platform_error() @@ -391,7 +439,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - sync.guard(&f.impl.p_mutex) + sync.guard(&f.p_mutex) p, offset := p, offset for len(p) > 0 { m := pwrite(f, p, offset) or_return @@ -402,12 +450,12 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } -_file_size :: proc(f: ^File) -> (n: i64, err: Error) { +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER - if f.impl.kind == .Pipe { + if f.kind == .Pipe { return 0, .No_Size } - handle := _handle(f) + handle := _handle(&f.file) if !win32.GetFileSizeEx(handle, &length) { err = _get_platform_error() } @@ -417,11 +465,17 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { - return _flush(f) + if f != nil && f.impl != nil { + return _flush((^File_Impl)(f.impl)) + } + return nil } -_flush :: proc(f: ^File) -> Error { - handle := _handle(f) +_flush :: proc(f: ^File_Impl) -> Error { + return _flush(f) +} +_flush_internal :: proc(f: ^File_Impl) -> Error { + handle := _handle(&f.file) if !win32.FlushFileBuffers(handle) { return _get_platform_error() } @@ -429,7 +483,7 @@ _flush :: proc(f: ^File) -> Error { } _truncate :: proc(f: ^File, size: i64) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } curr_off := seek(f, 0, .Current) or_return @@ -443,7 +497,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - p := _fix_long_path(name) + TEMP_ALLOCATOR_GUARD() + p := _fix_long_path(name, temp_allocator()) or_return err, err1: Error if !win32.DeleteFileW(p) { err = _get_platform_error() @@ -480,8 +535,9 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_path, new_path: string) -> Error { - from := _fix_long_path(old_path) - to := _fix_long_path(new_path) + TEMP_ALLOCATOR_GUARD() + from := _fix_long_path(old_path, temp_allocator()) or_return + to := _fix_long_path(new_path, temp_allocator()) or_return if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { return nil } @@ -489,10 +545,10 @@ _rename :: proc(old_path, new_path: string) -> Error { } - _link :: proc(old_name, new_name: string) -> Error { - o := _fix_long_path(old_name) - n := _fix_long_path(new_name) + TEMP_ALLOCATOR_GUARD() + o := _fix_long_path(old_name, temp_allocator()) or_return + n := _fix_long_path(new_name, temp_allocator()) or_return if win32.CreateHardLinkW(n, o, nil) { return nil } @@ -532,16 +588,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st } if !has_unc_prefix(p) { - return win32.utf16_to_utf8(p, allocator) + return win32_utf16_to_utf8(p, allocator) } ws := p[4:] switch { case len(ws) >= 2 && ws[1] == ':': - return win32.utf16_to_utf8(ws, allocator) + return win32_utf16_to_utf8(ws, allocator) case has_prefix(ws, `UNC\`): ws[3] = '\\' // override data in buffer - return win32.utf16_to_utf8(ws[3:], allocator) + return win32_utf16_to_utf8(ws[3:], allocator) } @@ -566,9 +622,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st ws = ws[4:] if len(ws) > 3 && has_prefix(ws, `UNC`) { ws[2] = '\\' - return win32.utf16_to_utf8(ws[2:], allocator) + return win32_utf16_to_utf8(ws[2:], allocator) } - return win32.utf16_to_utf8(ws, allocator) + return win32_utf16_to_utf8(ws, allocator) } return "", .Invalid_Path } @@ -579,7 +635,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er @thread_local rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - p := _fix_long_path(name) + TEMP_ALLOCATOR_GUARD() + + p := _fix_long_path(name, temp_allocator()) or_return handle := _open_sym_link(p) or_return defer win32.CloseHandle(handle) @@ -599,7 +657,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { - return win32.utf16_to_utf8(p, allocator) + return win32_utf16_to_utf8(p, allocator) } return _normalize_link_path(p, allocator) @@ -616,17 +674,18 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er _fchdir :: proc(f: ^File) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } - if !win32.SetCurrentDirectoryW(f.impl.wname) { + impl := (^File_Impl)(f.impl) + if !win32.SetCurrentDirectoryW(impl.wname) { return _get_platform_error() } return nil } -_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - if f == nil { +_fchmod :: proc(f: ^File, mode: int) -> Error { + if f == nil || f.impl == nil { return nil } d: win32.BY_HANDLE_FILE_INFORMATION @@ -653,14 +712,15 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chdir :: proc(name: string) -> Error { - p := _fix_long_path(name) + TEMP_ALLOCATOR_GUARD() + p := _fix_long_path(name, temp_allocator()) or_return if !win32.SetCurrentDirectoryW(p) { return _get_platform_error() } return nil } -_chmod :: proc(name: string, mode: File_Mode) -> Error { +_chmod :: proc(name: string, mode: int) -> Error { f := open(name, {.Write}) or_return defer close(f) return _fchmod(f, mode) @@ -681,7 +741,7 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return _fchtimes(f, atime, mtime) } _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } d: win32.BY_HANDLE_FILE_INFORMATION @@ -708,36 +768,16 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { return nil } - - _exists :: proc(path: string) -> bool { - wpath := _fix_long_path(path) + TEMP_ALLOCATOR_GUARD() + wpath, _ := _fix_long_path(path, temp_allocator()) attribs := win32.GetFileAttributesW(wpath) return attribs != win32.INVALID_FILE_ATTRIBUTES } -_is_file :: proc(path: string) -> bool { - wpath := _fix_long_path(path) - attribs := win32.GetFileAttributesW(wpath) - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 - } - return false -} - -_is_dir :: proc(path: string) -> bool { - wpath := _fix_long_path(path) - attribs := win32.GetFileAttributesW(wpath) - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 - } - return false -} - - @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) ferr: Error switch mode { case .Read: @@ -778,3 +818,86 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, return 0, .Empty } + + + +@(private="package", require_results) +win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: [^]u16, err: runtime.Allocator_Error) { + ws = raw_data(win32_utf8_to_utf16(s, allocator) or_return) + return +} + +@(private="package", require_results) +win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) { + if len(s) < 1 { + return + } + + b := transmute([]byte)s + cstr := raw_data(b) + n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0) + if n == 0 { + return nil, nil + } + + text := make([]u16, n+1, allocator) or_return + + n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n) + if n1 == 0 { + delete(text, allocator) + return + } + + text[n] = 0 + for n >= 1 && text[n-1] == 0 { + n -= 1 + } + ws = text[:n] + return +} + +@(private="package", require_results) +win32_wstring_to_utf8 :: proc(s: [^]u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if s == nil || s[0] == 0 { + return "", nil + } + n := 0 + for s[n] != 0 { + n += 1 + } + return win32_utf16_to_utf8(s[:n], allocator) +} + +@(private="package", require_results) +win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if len(s) == 0 { + return + } + + n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0, nil, nil) + if n == 0 { + return + } + + // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated + // and will scan it to find the first null terminated character. The resulting string will + // also be null terminated. + // If N > 0 it assumes the wide string is not null terminated and the resulting string + // will not be null terminated. + text := make([]byte, n, allocator) or_return + + n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(text), n, nil, nil) + if n1 == 0 { + delete(text, allocator) + return + } + + for i in 0.. ([]byte, runtime.Allocator_Error) { return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc) } - - -@(private) -error_allocator := heap_allocator diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index e26cf7439..f7a38f3f1 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -126,3 +126,5 @@ random_string :: proc(buf: []byte) -> string { buf[i] = digits[u % b] return string(buf[i:]) } + + diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 27c3d6b0b..3bf422ccb 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -12,12 +12,14 @@ is_path_separator :: proc(c: byte) -> bool { } mkdir :: make_directory -make_directory :: proc(name: string, perm: File_Mode) -> Error { + +make_directory :: proc(name: string, perm: int) -> Error { return _mkdir(name, perm) } mkdir_all :: make_directory_all -make_directory_all :: proc(path: string, perm: File_Mode) -> Error { + +make_directory_all :: proc(path: string, perm: int) -> Error { return _mkdir_all(path, perm) } @@ -25,14 +27,15 @@ remove_all :: proc(path: string) -> Error { return _remove_all(path) } - getwd :: get_working_directory + @(require_results) get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _getwd(allocator) + return _get_working_directory(allocator) } setwd :: set_working_directory + set_working_directory :: proc(dir: string) -> (err: Error) { - return _setwd(dir) + return _set_working_directory(dir) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 3c08eedee..be60f9b86 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -15,19 +15,13 @@ _is_path_separator :: proc(c: byte) -> bool { return c == '/' } -_mkdir :: proc(path: string, perm: File_Mode) -> Error { - // TODO: These modes would require mknod, however, that would also - // require additional arguments to this function.. - if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { - return .Invalid_Argument - } - +_mkdir :: proc(path: string, perm: int) -> Error { TEMP_ALLOCATOR_GUARD() path_cstr := temp_cstring(path) or_return - return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)(u32(perm) & 0o777))) + return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { +_mkdir_all :: proc(path: string, perm: int) -> Error { mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error { i: int for ; i < len(path) - 1 && path[i] != '/'; i += 1 {} @@ -38,7 +32,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) #partial switch errno { case .ENOENT: - if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)(u32(perm))); errno != .NONE { + if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE { return _get_platform_error(errno) } has_created^ = true @@ -53,17 +47,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { // skip consecutive '/' for i += 1; i < len(path) && path[i] == '/'; i += 1 {} return mkdirat(new_dfd, path[i:], perm, has_created) - case: - return _get_platform_error(errno) } - unreachable() + return _get_platform_error(errno) } - - // TODO - if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { - return .Invalid_Argument - } - TEMP_ALLOCATOR_GUARD() // need something we can edit, and use to generate cstrings path_bytes := make([]u8, len(path) + 1, temp_allocator()) @@ -85,12 +71,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } has_created: bool - mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return - if has_created { - return nil - } - return .Exist - //return has_created ? nil : .Exist + mkdirat(dfd, path_bytes, perm, &has_created) or_return + return nil if has_created else .Exist } dirent64 :: struct { @@ -181,7 +163,7 @@ _remove_all :: proc(path: string) -> Error { return _get_platform_error(linux.rmdir(path_cstr)) } -_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { +_get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -201,12 +183,12 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { unreachable() } -_setwd :: proc(dir: string) -> Error { +_set_working_directory :: proc(dir: string) -> Error { dir_cstr := temp_cstring(dir) or_return return _get_platform_error(linux.chdir(dir_cstr)) } -_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string { +_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { PROC_FD_PATH :: "/proc/self/fd/" buf: [32]u8 @@ -214,10 +196,9 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string { strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) - fullpath: string - err: Error if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { - return "" + delete(fullpath, allocator) + fullpath = "" } - return fullpath + return } diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index fcd1e3321..4aa695ee2 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -12,14 +12,15 @@ _is_path_separator :: proc(c: byte) -> bool { return c == '\\' || c == '/' } -_mkdir :: proc(name: string, perm: File_Mode) -> Error { - if !win32.CreateDirectoryW(_fix_long_path(name), nil) { +_mkdir :: proc(name: string, perm: int) -> Error { + TEMP_ALLOCATOR_GUARD() + if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { return _get_platform_error() } return nil } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { +_mkdir_all :: proc(path: string, perm: int) -> Error { fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { if len(p) == len(`\\?\c:`) { if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { @@ -33,9 +34,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { TEMP_ALLOCATOR_GUARD() - dir, err := stat(path, temp_allocator()) + dir_stat, err := stat(path, temp_allocator()) if err == nil { - if dir.is_directory { + if dir_stat.type == .Directory { return nil } return .Exist @@ -61,8 +62,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { err = mkdir(path, perm) if err != nil { - dir1, err1 := lstat(path, temp_allocator()) - if err1 == nil && dir1.is_directory { + new_dir_stat, err1 := lstat(path, temp_allocator()) + if err1 == nil && new_dir_stat.type == .Directory { return nil } return err @@ -71,41 +72,114 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } _remove_all :: proc(path: string) -> Error { - // TODO(bill): _remove_all for windows + if path == "" { + return nil + } + + err := remove(path) + if err == nil || err == .Not_Exist { + return nil + } + + TEMP_ALLOCATOR_GUARD() + dir := win32_utf8_to_wstring(path, temp_allocator()) or_return + + empty: [1]u16 + + file_op := win32.SHFILEOPSTRUCTW { + nil, + win32.FO_DELETE, + dir, + &empty[0], + win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT, + false, + nil, + &empty[0], + } + res := win32.SHFileOperationW(&file_op) + if res != 0 { + return _get_platform_error() + } return nil } -_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - // TODO(bill) - return "", nil +@private cwd_lock: win32.SRWLOCK // zero is initialized + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + win32.AcquireSRWLockExclusive(&cwd_lock) + + TEMP_ALLOCATOR_GUARD() + + sz_utf16 := win32.GetCurrentDirectoryW(0, nil) + dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return + + sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) + assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return win32_utf16_to_utf8(dir_buf_wstr, allocator) } -_setwd :: proc(dir: string) -> (err: Error) { - // TODO(bill) - return nil -} +_set_working_directory :: proc(dir: string) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return + win32.AcquireSRWLockExclusive(&cwd_lock) + + if !win32.SetCurrentDirectoryW(wstr) { + err = _get_platform_error() + } + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return +} can_use_long_paths: bool @(init) init_long_path_support :: proc() { - // TODO(bill): init_long_path_support - // ADD THIS SHIT - // registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`) can_use_long_paths = false + + key: win32.HKEY + res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key) + defer win32.RegCloseKey(key) + if res != 0 { + return + } + + value: u32 + size := u32(size_of(value)) + res = win32.RegGetValueW( + key, + nil, + win32.L("LongPathsEnabled"), + win32.RRF_RT_ANY, + nil, + &value, + &size, + ) + if res != 0 { + return + } + if value == 1 { + can_use_long_paths = true + } + } - -_fix_long_path_slice :: proc(path: string) -> []u16 { - return win32.utf8_to_utf16(_fix_long_path_internal(path)) +@(require_results) +_fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) { + return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator) } -_fix_long_path :: proc(path: string) -> win32.wstring { - return win32.utf8_to_wstring(_fix_long_path_internal(path)) +@(require_results) +_fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) { + return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator) } - +@(require_results) _fix_long_path_internal :: proc(path: string) -> string { if can_use_long_paths { return path @@ -162,5 +236,4 @@ _fix_long_path_internal :: proc(path: string) -> string { } return string(path_buf[:w]) - } diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index 5d42cca78..c3fecfb9e 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -5,13 +5,13 @@ import "core:sys/linux" _pipe :: proc() -> (r, w: ^File, err: Error) { fds: [2]linux.Fd - errno := linux.pipe2(&fds, {.CLOEXEC}) + errno := linux.pipe2(&fds, {}) if errno != .NONE { return nil, nil,_get_platform_error(errno) } - r = _new_file(uintptr(fds[0])) - w = _new_file(uintptr(fds[1])) + r = _new_file(uintptr(fds[0])) or_return + w = _new_file(uintptr(fds[1])) or_return return } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index bab8b44f5..59615e306 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -5,7 +5,11 @@ import win32 "core:sys/windows" _pipe :: proc() -> (r, w: ^File, err: Error) { p: [2]win32.HANDLE - if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = true, + } + if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { return nil, nil, _get_platform_error() } return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 862434b7b..3f3e64668 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -1,102 +1,406 @@ package os2 -import "core:sync" -import "core:time" import "base:runtime" +import "core:time" -args: []string +/* + In procedures that explicitly state this as one of the allowed values, + specifies an infinite timeout. +*/ +TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity +/* + Arguments to the current process. +*/ +args := get_args() + +@(private="file", require_results) +get_args :: proc() -> []string { + result := make([]string, len(runtime.args__), heap_allocator()) + for rt_arg, i in runtime.args__ { + result[i] = string(rt_arg) + } + return result +} + +/* + Exit the current process. +*/ exit :: proc "contextless" (code: int) -> ! { - runtime.trap() + _exit(code) } +/* + Obtain the UID of the current process. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_uid :: proc() -> int { - return -1 + return _get_uid() } +/* + Obtain the effective UID of the current process. + + The effective UID is typically the same as the UID of the process. In case + the process was run by a user with elevated permissions, the process may + lower the privilege to perform some tasks without privilege. In these cases + the real UID of the process and the effective UID are different. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_euid :: proc() -> int { - return -1 + return _get_euid() } +/* + Obtain the GID of the current process. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_gid :: proc() -> int { - return -1 + return _get_gid() } +/* + Obtain the effective GID of the current process. + + The effective GID is typically the same as the GID of the process. In case + the process was run by a user with elevated permissions, the process may + lower the privilege to perform some tasks without privilege. In these cases + the real GID of the process and the effective GID are different. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ +@(require_results) get_egid :: proc() -> int { - return -1 + return _get_egid() } +/* + Obtain the ID of the current process. +*/ +@(require_results) get_pid :: proc() -> int { - return -1 + return _get_pid() } +/* + Obtain the ID of the parent process. + + **Note(windows)**: Windows does not mantain strong relationships between + parent and child processes. This function returns the ID of the process + that has created the current process. In case the parent has died, the ID + returned by this function can identify a non-existent or a different + process. +*/ +@(require_results) get_ppid :: proc() -> int { - return -1 + return _get_ppid() } +/* + Obtain ID's of all processes running in the system. +*/ +@(require_results) +process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { + return _process_list(allocator) +} +/* + Bit set specifying which fields of the `Process_Info` struct need to be + obtained by the `process_info()` procedure. Each bit corresponds to a + field in the `Process_Info` struct. +*/ +Process_Info_Fields :: bit_set[Process_Info_Field] +Process_Info_Field :: enum { + Executable_Path, + PPid, + Priority, + Command_Line, + Command_Args, + Environment, + Username, + Working_Dir, +} + +/* + Contains information about the process as obtained by the `process_info()` + procedure. +*/ +Process_Info :: struct { + // The information about a process the struct contains. `pid` is always + // stored, no matter what. + fields: Process_Info_Fields, + // The ID of the process. + pid: int, + // The ID of the parent process. + ppid: int, + // The process priority. + priority: int, + // The path to the executable, which the process runs. + executable_path: string, + // The command line supplied to the process. + command_line: string, + // The arguments supplied to the process. + command_args: []string, + // The environment of the process. + environment: []string, + // The username of the user who started the process. + username: string, + // The current working directory of the process. + working_dir: string, +} + +/* + Obtain information about a process. + + This procedure obtains an information, specified by `selection` parameter of + a process given by `pid`. + + Use `free_process_info` to free the memory allocated by this procedure. In + case the function returns an error all temporary allocations would be freed + and as such, calling `free_process_info()` is not needed. + + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. +*/ +@(require_results) +process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_pid(pid, selection, allocator) +} + +/* + Obtain information about a process. + + This procedure obtains information, specified by `selection` parameter + about a process that has been opened by the application, specified in + the `process` parameter. + + Use `free_process_info` to free the memory allocated by this procedure. In + case the function returns an error, all temporary allocations would be freed + and as such, calling `free_process_info` is not needed. + + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. +*/ +@(require_results) +process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_handle(process, selection, allocator) +} + +/* + Obtain information about the current process. + + This procedure obtains the information, specified by `selection` parameter + about the currently running process. + + Use `free_process_info` to free the memory allocated by this function. In + case this function returns an error, all temporary allocations would be + freed and as such calling `free_process_info()` is not needed. + + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. +*/ +@(require_results) +current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _current_process_info(selection, allocator) +} + +/* + Obtain information about the specified process. +*/ +process_info :: proc { + process_info_by_pid, + process_info_by_handle, + current_process_info, +} + +/* + Free the information about the process. + + This procedure frees the memory occupied by process info using the provided + allocator. The allocator needs to be the same allocator that was supplied + to the `process_info` function. +*/ +free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { + delete(pi.executable_path, allocator) + delete(pi.command_line, allocator) + delete(pi.command_args, allocator) + for s in pi.environment { + delete(s, allocator) + } + delete(pi.environment, allocator) + delete(pi.working_dir, allocator) +} + +/* + Represents a process handle. + + When a process dies, the OS is free to re-use the pid of that process. The + `Process` struct represents a handle to the process that will refer to a + specific process, even after it has died. + + **Note(linux)**: The `handle` will be referring to pidfd. +*/ Process :: struct { - pid: int, - handle: uintptr, - is_done: b32, - signal_mutex: sync.RW_Mutex, + pid: int, + handle: uintptr, } +Process_Open_Flags :: bit_set[Process_Open_Flag] +Process_Open_Flag :: enum { + // Request for reading from the virtual memory of another process. + Mem_Read, + // Request for writing to the virtual memory of another process. + Mem_Write, +} -Process_Attributes :: struct { - dir: string, +/* + Open a process handle using it's pid. + + This procedure obtains a process handle of a process specified by `pid`. + This procedure can be subject to race conditions. See the description of + `Process`. + + Use `process_close()` function to close the process handle. +*/ +@(require_results) +process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) { + return _process_open(pid, flags) +} + +/* + The description of how a process should be created. +*/ +Process_Desc :: struct { + // OS-specific attributes. + sys_attr: _Sys_Process_Attributes, + // The working directory of the process. If the string has length 0, the + // working directory is assumed to be the current working directory of the + // current process. + working_dir: string, + // The command to run. Each element of the slice is a separate argument to + // the process. The first element of the slice would be the executable. + command: []string, + // A slice of strings, each having the format `KEY=VALUE` representing the + // full environment that the child process will receive. + // In case this slice is `nil`, the current process' environment is used. env: []string, - files: []^File, - sys: ^Process_Attributes_OS_Specific, + // The `stderr` handle to give to the child process. It can be either a file + // or a writeable end of a pipe. Passing `nil` will shut down the process' + // stderr output. + stderr: ^File, + // The `stdout` handle to give to the child process. It can be either a file + // or a writeabe end of a pipe. Passing a `nil` will shut down the process' + // stdout output. + stdout: ^File, + // The `stdin` handle to give to the child process. It can either be a file + // or a readable end of a pipe. Passing a `nil` will shut down the process' + // input. + stdin: ^File, } -Process_Attributes_OS_Specific :: struct{} +/* + Create a new process and obtain its handle. -Process_Error :: enum { - None, + This procedure creates a new process, with a given command and environment + strings as parameters. Use `environ()` to inherit the environment of the + current process. + + The `desc` parameter specifies the description of how the process should + be created. It contains information such as the command line, the + environment of the process, the starting directory and many other options. + Most of the fields in the struct can be set to `nil` or an empty value. + + Use `process_close` to close the handle to the process. Note, that this + is not the same as terminating the process. One can terminate the process + and not close the handle, in which case the handle would be leaked. In case + the function returns an error, an invalid handle is returned. + + This procedure is not thread-safe. It may alter the inheritance properties + of file handles in an unpredictable manner. In case multiple threads change + handle inheritance properties, make sure to serialize all those calls. +*/ +@(require_results) +process_start :: proc(desc := Process_Desc {}) -> (Process, Error) { + return _process_start(desc) } +/* + The state of the process after it has finished execution. +*/ Process_State :: struct { - pid: int, - exit_code: int, - exited: bool, - success: bool, + // The ID of the process. + pid: int, + // Specifies whether the process has terminated or is still running. + exited: bool, + // The exit code of the process, if it has exited. + // Will also store the number of the exception or signal that has crashed the + // process. + exit_code: int, + // Specifies whether the termination of the process was successfull or not, + // i.e. whether it has crashed or not. + // **Note(windows)**: On windows `true` is always returned, as there is no + // reliable way to obtain information about whether the process has crashed. + success: bool, + // The time the process has spend executing in kernel time. system_time: time.Duration, - user_time: time.Duration, - sys: rawptr, + // The time the process has spend executing in userspace. + user_time: time.Duration, } -Signal :: #type proc() +/* + Wait for a process event. -Kill: Signal = nil -Interrupt: Signal = nil + This procedure blocks the execution until the process has exited or the + timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, + no timeout restriction is imposed and the procedure can block indefinately. + If the timeout has expired, the `General_Error.Timeout` is returned as + the error. -find_process :: proc(pid: int) -> (^Process, Process_Error) { - return nil, .None + If an error is returned for any other reason, other than timeout, the + process state is considered undetermined. +*/ +@(require_results) +process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { + return _process_wait(process, timeout) } +/* + Close the handle to a process. -process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { - return nil, .None + This procedure closes the handle associated with a process. It **does not** + terminate a process, in case it was running. In case a termination is + desired, kill the process first, wait for the process to finish, + then close the handle. +*/ +@(require_results) +process_close :: proc(process: Process) -> (Error) { + return _process_close(process) } -process_release :: proc(p: ^Process) -> Process_Error { - return .None +/* + Terminate a process. + + This procedure terminates a process, specified by it's handle, `process`. + +*/ +@(require_results) +process_kill :: proc(process: Process) -> (Error) { + return _process_kill(process) } - -process_kill :: proc(p: ^Process) -> Process_Error { - return .None -} - -process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { - return .None -} - -process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { - return {}, .None -} - - - - diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin new file mode 100644 index 000000000..d832083b6 --- /dev/null +++ b/core/os/os2/process_linux.odin @@ -0,0 +1,95 @@ +//+private file +package os2 + +import "base:runtime" +import "core:time" +import "core:sys/linux" + +@(private="package") +_exit :: proc "contextless" (code: int) -> ! { + linux.exit(i32(code)) +} + + +@(private="package") +_get_uid :: proc() -> int { + return -1 +} + +@(private="package") +_get_euid :: proc() -> int { + return -1 +} + +@(private="package") +_get_gid :: proc() -> int { + return -1 +} + +@(private="package") +_get_egid :: proc() -> int { + return -1 +} + +@(private="package") +_get_pid :: proc() -> int { + return -1 +} + +@(private="package") +_get_ppid :: proc() -> int { + return -1 +} + +@(private="package") +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + return +} + +@(private="package") +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return +} + +@(private="package") +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + return +} + +@(private="package") +_Sys_Process_Attributes :: struct {} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + return +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + return +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + return nil +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + return nil +} + +@(private="package") +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { + return +} \ No newline at end of file diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin new file mode 100644 index 000000000..47fd62401 --- /dev/null +++ b/core/os/os2/process_windows.odin @@ -0,0 +1,695 @@ +//+private file +package os2 + +import "base:runtime" + +import "core:strings" +import win32 "core:sys/windows" +import "core:time" + +@(private="package") +_exit :: proc "contextless" (code: int) -> ! { + win32.ExitProcess(u32(code)) +} + +@(private="package") +_get_uid :: proc() -> int { + return -1 +} + +@(private="package") +_get_euid :: proc() -> int { + return -1 +} + +@(private="package") +_get_gid :: proc() -> int { + return -1 +} + +@(private="package") +_get_egid :: proc() -> int { + return -1 +} + +@(private="package") +_get_pid :: proc() -> int { + return int(win32.GetCurrentProcessId()) +} + +@(private="package") +_get_ppid :: proc() -> int { + our_pid := win32.GetCurrentProcessId() + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + return -1 + } + defer win32.CloseHandle(snap) + entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + for status := win32.Process32FirstW(snap, &entry); status; /**/ { + if entry.th32ProcessID == our_pid { + return int(entry.th32ParentProcessID) + } + status = win32.Process32NextW(snap, &entry) + } + return -1 +} + +@(private="package") +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + + list_d := make([dynamic]int, allocator) or_return + + entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} + status := win32.Process32FirstW(snap, &entry) + for status { + append(&list_d, int(entry.th32ProcessID)) + status = win32.Process32NextW(snap, &entry) + } + list = list_d[:] + return +} + +@(require_results) +read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} +@(require_results) +read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} + +@(private="package") +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = pid + defer if err != nil { + free_process_info(info, allocator) + } + + // Data obtained from process snapshots + if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + if .Executable_Path in selection { // snap module + info.executable_path = _process_exe_by_pid(pid, allocator) or_return + info.fields += {.Executable_Path} + } + + ph := win32.INVALID_HANDLE_VALUE + + if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle + ph = win32.OpenProcess( + win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, + false, + u32(pid), + ) + if ph == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + } + defer if ph != win32.INVALID_HANDLE_VALUE { + win32.CloseHandle(ph) + } + + if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + if process_info.PebBaseAddress == nil { + // Not sure what the error is + err = General_Error.Unsupported + return + } + process_peb: win32.PEB + + _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return + + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return + + if selection >= {.Command_Line, .Command_Args} { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return + + if .Command_Line in selection { + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return + + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + if .Working_Dir in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return + + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + + if .Username in selection { + info.username = _get_process_user(ph, allocator) or_return + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + pid := process.pid + info.pid = pid + defer if err != nil { + free_process_info(info, allocator) + } + + // Data obtained from process snapshots + if selection >= {.PPid, .Priority} { // snap process + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + if .Executable_Path in selection { // snap module + info.executable_path = _process_exe_by_pid(pid, allocator) or_return + info.fields += {.Executable_Path} + } + ph := win32.HANDLE(process.handle) + if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + if process_info.PebBaseAddress == nil { + // Not sure what the error is + err = General_Error.Unsupported + return + } + + process_peb: win32.PEB + _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return + + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return + + if selection >= {.Command_Line, .Command_Args} { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return + + if .Command_Line in selection { + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.fields += {.Command_Args} + } + } + + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return + + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + + if .Working_Dir in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return + + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + if .Username in selection { + info.username = _get_process_user(ph, allocator) or_return + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = get_pid() + defer if err != nil { + free_process_info(info, allocator) + } + + if selection >= {.PPid, .Priority} { // snap process + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + if .Executable_Path in selection { + exe_filename_w: [256]u16 + path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return + info.fields += {.Executable_Path} + } + if selection >= {.Command_Line, .Command_Args} { + command_line_w := win32.GetCommandLineW() + if .Command_Line in selection { + info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(command_line_w, allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + env_block := win32.GetEnvironmentStringsW() + info.environment = _parse_environment_block(env_block, allocator) or_return + info.fields += {.Environment} + } + if .Username in selection { + process_handle := win32.GetCurrentProcess() + info.username = _get_process_user(process_handle, allocator) or_return + info.fields += {.Username} + } + if .Working_Dir in selection { + // TODO(flysand): Implement this by reading PEB + err = .Mode_Not_Implemented + return + } + err = nil + return +} + +@(private="package") +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + // Note(flysand): The handle will be used for querying information so we + // take the necessary permissions right away. + dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE + if .Mem_Read in flags { + dwDesiredAccess |= win32.PROCESS_VM_READ + } + if .Mem_Write in flags { + dwDesiredAccess |= win32.PROCESS_VM_WRITE + } + handle := win32.OpenProcess( + dwDesiredAccess, + false, + u32(pid), + ) + if handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + } else { + process = {pid = pid, handle = uintptr(handle)} + } + return +} + +@(private="package") +_Sys_Process_Attributes :: struct {} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + TEMP_ALLOCATOR_GUARD() + command_line := _build_command_line(desc.command, temp_allocator()) + command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return + environment := desc.env + if desc.env == nil { + environment = environ(temp_allocator()) + } + environment_block := _build_environment_block(environment, temp_allocator()) + environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return + stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE) + stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) + stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE) + + if desc.stdout != nil { + stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) + } + if desc.stderr != nil { + stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + if desc.stdin != nil { + stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + + working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil + process_info: win32.PROCESS_INFORMATION + ok := win32.CreateProcessW( + nil, + command_line_w, + nil, + nil, + true, + win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS, + raw_data(environment_block_w), + working_dir_w, + &win32.STARTUPINFOW{ + cb = size_of(win32.STARTUPINFOW), + hStdError = stderr_handle, + hStdOutput = stdout_handle, + hStdInput = stdin_handle, + dwFlags = win32.STARTF_USESTDHANDLES, + }, + &process_info, + ) + if !ok { + err = _get_platform_error() + return + } + process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)} + return +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + handle := win32.HANDLE(process.handle) + timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE + + switch win32.WaitForSingleObject(handle, timeout_ms) { + case win32.WAIT_OBJECT_0: + exit_code: u32 + if !win32.GetExitCodeProcess(handle, &exit_code) { + err =_get_platform_error() + return + } + time_created: win32.FILETIME + time_exited: win32.FILETIME + time_kernel: win32.FILETIME + time_user: win32.FILETIME + if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { + err = _get_platform_error() + return + } + process_state = { + exit_code = int(exit_code), + exited = true, + pid = process.pid, + success = true, + system_time = _filetime_to_duration(time_kernel), + user_time = _filetime_to_duration(time_user), + } + return + case win32.WAIT_TIMEOUT: + err = General_Error.Timeout + return + case: + err = _get_platform_error() + return + } +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + if !win32.CloseHandle(win32.HANDLE(process.handle)) { + return _get_platform_error() + } + return nil +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + // Note(flysand): This is different than what the task manager's "kill process" + // functionality does, as we don't try to send WM_CLOSE message first. This + // is quite a rough way to kill the process, which should be consistent with + // linux. The error code 9 is to mimic SIGKILL event. + if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) { + return _get_platform_error() + } + return nil +} + +_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration { + ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) + return time.Duration(ticks * 100) +} + +_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} + status := win32.Process32FirstW(snap, &entry) + for status { + if u32(pid) == entry.th32ProcessID { + return + } + status = win32.Process32NextW(snap, &entry) + } + err = General_Error.Not_Exist + return +} + +// Note(flysand): Not sure which way it's better to get the executable path: +// via toolhelp snapshots or by reading other process' PEB memory. I have +// a slight suspicion that if both exe path and command line are desired, +// it's faster to just read both from PEB, but maybe the toolhelp snapshots +// are just better...? +@(private="package") +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { + snap := win32.CreateToolhelp32Snapshot( + win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32, + u32(pid), + ) + if snap == win32.INVALID_HANDLE_VALUE { + err =_get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) } + status := win32.Module32FirstW(snap, &entry) + if !status { + err =_get_platform_error() + return + } + return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator) +} + +_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + token_handle: win32.HANDLE + if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { + err = _get_platform_error() + return + } + token_user_size: u32 + if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { + // Note(flysand): Make sure the buffer too small error comes out, and not any other error + err = _get_platform_error() + if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) { + return + } + err = nil + } + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) + if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { + err = _get_platform_error() + return + } + + sid_type: win32.SID_NAME_USE + username_w: [256]u16 + domain_w: [256]u16 + username_chrs := u32(256) + domain_chrs := u32(256) + + if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { + err = _get_platform_error() + return + } + username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return + domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return + return strings.concatenate({domain, "\\", username}, allocator) +} + +_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { + argc: i32 + argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc) + if argv_w == nil { + return nil, _get_platform_error() + } + argv = make([]string, argc, allocator) or_return + defer if err != nil { + for arg in argv { + delete(arg, allocator) + } + delete(argv, allocator) + } + for arg_w, i in argv_w[:argc] { + argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return + } + return +} + +_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string { + _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) { + for _ in 0 ..< n { + strings.write_byte(builder, b) + } + } + builder := strings.builder_make(allocator) + for arg, i in command { + if i != 0 { + strings.write_byte(&builder, ' ') + } + j := 0 + strings.write_byte(&builder, '"') + for j < len(arg) { + backslashes := 0 + for j < len(arg) && arg[j] == '\\' { + backslashes += 1 + j += 1 + } + if j == len(arg) { + _write_byte_n_times(&builder, '\\', 2*backslashes) + break + } else if arg[j] == '"' { + _write_byte_n_times(&builder, '\\', 2*backslashes+1) + strings.write_byte(&builder, '"') + } else { + _write_byte_n_times(&builder, '\\', backslashes) + strings.write_byte(&builder, arg[j]) + } + j += 1 + } + strings.write_byte(&builder, '"') + } + return strings.to_string(builder) +} + +_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) { + zt_count := 0 + for idx := 0; true; { + if block[idx] == 0x0000 { + zt_count += 1 + if block[idx+1] == 0x0000 { + zt_count += 1 + break + } + } + idx += 1 + } + + // Note(flysand): Each string in the environment block is terminated + // by a NUL character. In addition, the environment block itself is + // terminated by a NUL character. So the number of strings in the + // environment block is the number of NUL character minus the + // block terminator. + env_count := zt_count - 1 + envs = make([]string, env_count, allocator) or_return + defer if err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs, allocator) + } + + env_idx := 0 + last_idx := 0 + idx := 0 + for block[idx] != 0x0000 { + for block[idx] != 0x0000 { + idx += 1 + } + env_w := block[last_idx:idx] + envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return + env_idx += 1 + idx += 1 + last_idx = idx + } + return +} + +_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string { + builder := strings.builder_make(allocator) + loop: #reverse for kv, cur_idx in environment { + eq_idx := strings.index_byte(kv, '=') + assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values") + key := kv[:eq_idx] + for old_kv in environment[cur_idx+1:] { + old_key := old_kv[:strings.index_byte(old_kv, '=')] + if key == old_key { + continue loop + } + } + strings.write_string(&builder, kv) + strings.write_byte(&builder, 0) + } + // Note(flysand): In addition to the NUL-terminator for each string, the + // environment block itself is NUL-terminated. + strings.write_byte(&builder, 0) + return strings.to_string(builder) +} diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index f79ad9165..b3ca47be3 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -1,21 +1,34 @@ package os2 -import "core:time" import "base:runtime" +import "core:path/filepath" +import "core:strings" +import "core:time" Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) File_Info :: struct { fullpath: string, name: string, - size: i64, - mode: File_Mode, - is_directory: bool, + + inode: u128, // might be zero if cannot be determined + size: i64 `fmt:"M"`, + mode: int `fmt:"o"`, + type: File_Type, + creation_time: time.Time, modification_time: time.Time, access_time: time.Time, } +@(require_results) +file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) { + cloned = fi + cloned.fullpath = strings.clone(fi.fullpath) or_return + cloned.name = filepath.base(cloned.fullpath) + return +} + file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { for i := len(infos)-1; i >= 0; i -= 1 { file_info_delete(infos[i], allocator) @@ -29,10 +42,12 @@ file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { @(require_results) fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f != nil && f.user_fstat != nil { - return f->user_fstat(allocator) + if f == nil { + return {}, nil + } else if f.fstat != nil { + return f->fstat(allocator) } - return _fstat(f, allocator) + return {}, .Invalid_Callback } @(require_results) @@ -41,6 +56,7 @@ stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { } lstat :: stat_do_not_follow_links + @(require_results) stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _lstat(name, allocator) @@ -51,3 +67,21 @@ stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> same_file :: proc(fi1, fi2: File_Info) -> bool { return _same_file(fi1, fi2) } + + +last_write_time :: modification_time +last_write_time_by_name :: modification_time_by_path + +@(require_results) +modification_time :: proc(f: ^File) -> (time.Time, Error) { + TEMP_ALLOCATOR_GUARD() + fi, err := fstat(f, temp_allocator()) + return fi.modification_time, err +} + +@(require_results) +modification_time_by_path :: proc(path: string) -> (time.Time, Error) { + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + return fi.modification_time, err +} diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index c0b3088b4..6ccac1be0 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -7,31 +7,43 @@ import "core:sys/linux" import "core:path/filepath" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - return _fstat_internal(f.impl.fd, allocator) + impl := (^File_Impl)(f.impl) + return _fstat_internal(impl.fd, allocator) } -_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) { +_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { s: linux.Stat errno := linux.fstat(fd, &s) if errno != .NONE { return {}, _get_platform_error(errno) } + type := File_Type.Regular + switch s.mode & linux.S_IFMT { + case linux.S_IFBLK: type = .Block_Device + case linux.S_IFCHR: type = .Character_Device + case linux.S_IFDIR: type = .Directory + case linux.S_IFIFO: type = .Named_Pipe + case linux.S_IFLNK: type = .Symlink + case linux.S_IFREG: type = .Regular + case linux.S_IFSOCK: type = .Socket + } + mode := int(0o7777 & transmute(u32)s.mode) // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time - fi := File_Info { - fullpath = _get_full_path(fd, allocator), - name = "", - size = i64(s.size), - mode = 0, - is_directory = linux.S_ISDIR(s.mode), + fi = File_Info { + fullpath = _get_full_path(fd, allocator) or_return, + name = "", + inode = u128(u64(s.ino)), + size = i64(s.size), + mode = mode, + type = type, modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, - access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, - creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this + access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, + creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this } fi.creation_time = fi.modification_time - fi.name = filepath.base(fi.fullpath) - return fi, nil + return } // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 03ad2052f..5e66507be 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -6,41 +6,39 @@ import "core:time" import "core:strings" import win32 "core:sys/windows" -_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f == nil || f.impl.fd == nil { - return {}, nil +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || (^File_Impl)(f.impl).fd == nil { + return } - path, err := _cleanpath_from_handle(f, allocator) - if err != nil { - return {}, err - } + path := _cleanpath_from_handle(f, allocator) or_return h := _handle(f) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: - fi: File_Info - fi.fullpath = path - fi.name = basename(path) - fi.mode |= file_type_mode(h) - return fi, nil + fi = File_Info { + fullpath = path, + name = basename(path), + type = file_type(h), + } + return } return _file_info_from_get_file_information_by_handle(path, h, allocator) } + _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) } + _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) } + _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } - - - full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { name := name if name == "" { @@ -48,7 +46,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path } TEMP_ALLOCATOR_GUARD() - p := win32.utf8_to_utf16(name, temp_allocator()) + p := win32_utf8_to_utf16(name, temp_allocator()) or_return n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) if n == 0 { @@ -59,16 +57,16 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path if n == 0 { return "", _get_platform_error() } - return win32.utf16_to_utf8(buf[:n], allocator) + return win32_utf16_to_utf8(buf[:n], allocator) } - internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { return {}, .Not_Exist } + TEMP_ALLOCATOR_GUARD() - wname := _fix_long_path(name) + wname := _fix_long_path(name, temp_allocator()) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { @@ -99,7 +97,6 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt return _file_info_from_get_file_information_by_handle(name, h, allocator) } - _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { buf := buf N := 0 @@ -120,9 +117,8 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { return buf } - _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { - if f == nil || f.impl.fd == nil { + if f == nil { return "", nil } h := _handle(f) @@ -138,7 +134,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin } _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { - if f == nil || f.impl.fd == nil { + if f == nil { return nil, nil } h := _handle(f) @@ -156,10 +152,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { buf := buf buf = _cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, allocator) + return win32_utf16_to_utf8(buf, allocator) } - basename :: proc(name: string) -> (base: string) { name := name if len(name) > 3 && name[:3] == `\\?` { @@ -185,83 +180,67 @@ basename :: proc(name: string) -> (base: string) { return name } - -file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { +file_type :: proc(h: win32.HANDLE) -> File_Type { switch win32.GetFileType(h) { - case win32.FILE_TYPE_PIPE: - return File_Mode_Named_Pipe - case win32.FILE_TYPE_CHAR: - return File_Mode_Device | File_Mode_Char_Device + case win32.FILE_TYPE_PIPE: return .Named_Pipe + case win32.FILE_TYPE_CHAR: return .Character_Device + case win32.FILE_TYPE_DISK: return .Regular } - return 0 + return .Undetermined } - - -_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { +_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) { if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { mode |= 0o444 } else { mode |= 0o666 } - is_sym := false if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { is_sym = false } else { is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT } - if is_sym { - mode |= File_Mode_Sym_Link + type = .Symlink } else { if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - mode |= 0o111 | File_Mode_Dir + type = .Directory + mode |= 0o111 } - if h != nil { - mode |= file_type_mode(h) + type = file_type(h) } } - return } - _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } - _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } - _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { @@ -278,25 +257,20 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 } - fi: File_Info - fi.fullpath = path fi.name = basename(path) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow)) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - return fi, nil } - - reserved_names := [?]string{ "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", @@ -357,7 +331,6 @@ _volume_name_len :: proc(path: string) -> int { return 0 } - _is_abs :: proc(path: string) -> bool { if _is_reserved_name(path) { return true diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 3b3dbdd57..467775e89 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { attempts := 0 for { name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) - f, err = open(name, {.Read, .Write, .Create, .Excl}, File_Mode(0o666)) + f, err = open(name, {.Read, .Write, .Create, .Excl}, 0o666) if err == .Exist { close(f) attempts += 1 diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index 92afcde47..d6f90fbaf 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -3,8 +3,11 @@ package os2 import "base:runtime" - -_temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) { - //TODO - return "", nil +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + TEMP_ALLOCATOR_GUARD() + tmpdir := get_env("TMPDIR", temp_allocator()) + if tmpdir == "" { + tmpdir = "/tmp" + } + return clone_string(tmpdir, allocator) } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 4c8ab9fb7..d888eda52 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -19,5 +19,5 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er } else if n > 0 && b[n-1] == '\\' { n -= 1 } - return win32.utf16_to_utf8(b[:n], allocator) + return win32_utf16_to_utf8(b[:n], allocator) } diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index d29c22900..09cdd84d0 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -10,146 +10,284 @@ import "core:c" Handle :: distinct i32 File_Time :: distinct u64 -Errno :: distinct int INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno : 0 -EPERM: Errno : 1 /* Operation not permitted */ -ENOENT: Errno : 2 /* No such file or directory */ -ESRCH: Errno : 3 /* No such process */ -EINTR: Errno : 4 /* Interrupted system call */ -EIO: Errno : 5 /* Input/output error */ -ENXIO: Errno : 6 /* Device not configured */ -E2BIG: Errno : 7 /* Argument list too long */ -ENOEXEC: Errno : 8 /* Exec format error */ -EBADF: Errno : 9 /* Bad file descriptor */ -ECHILD: Errno : 10 /* No child processes */ -EDEADLK: Errno : 11 /* Resource deadlock avoided */ -ENOMEM: Errno : 12 /* Cannot allocate memory */ -EACCES: Errno : 13 /* Permission denied */ -EFAULT: Errno : 14 /* Bad address */ -ENOTBLK: Errno : 15 /* Block device required */ -EBUSY: Errno : 16 /* Device / Resource busy */ -EEXIST: Errno : 17 /* File exists */ -EXDEV: Errno : 18 /* Cross-device link */ -ENODEV: Errno : 19 /* Operation not supported by device */ -ENOTDIR: Errno : 20 /* Not a directory */ -EISDIR: Errno : 21 /* Is a directory */ -EINVAL: Errno : 22 /* Invalid argument */ -ENFILE: Errno : 23 /* Too many open files in system */ -EMFILE: Errno : 24 /* Too many open files */ -ENOTTY: Errno : 25 /* Inappropriate ioctl for device */ -ETXTBSY: Errno : 26 /* Text file busy */ -EFBIG: Errno : 27 /* File too large */ -ENOSPC: Errno : 28 /* No space left on device */ -ESPIPE: Errno : 29 /* Illegal seek */ -EROFS: Errno : 30 /* Read-only file system */ -EMLINK: Errno : 31 /* Too many links */ -EPIPE: Errno : 32 /* Broken pipe */ +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + ESRCH = 3, /* No such process */ + EINTR = 4, /* Interrupted system call */ + EIO = 5, /* Input/output error */ + ENXIO = 6, /* Device not configured */ + E2BIG = 7, /* Argument list too long */ + ENOEXEC = 8, /* Exec format error */ + EBADF = 9, /* Bad file descriptor */ + ECHILD = 10, /* No child processes */ + EDEADLK = 11, /* Resource deadlock avoided */ + ENOMEM = 12, /* Cannot allocate memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device / Resource busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* Operation not supported by device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* Too many open files in system */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Inappropriate ioctl for device */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + + /* math software */ + EDOM = 33, /* Numerical argument out of domain */ + ERANGE = 34, /* Result too large */ + + /* non-blocking and interrupt i/o */ + EAGAIN = 35, /* Resource temporarily unavailable */ + EWOULDBLOCK = EAGAIN, /* Operation would block */ + EINPROGRESS = 36, /* Operation now in progress */ + EALREADY = 37, /* Operation already in progress */ + + /* ipc/network software -- argument errors */ + ENOTSOCK = 38, /* Socket operation on non-socket */ + EDESTADDRREQ = 39, /* Destination address required */ + EMSGSIZE = 40, /* Message too long */ + EPROTOTYPE = 41, /* Protocol wrong type for socket */ + ENOPROTOOPT = 42, /* Protocol not available */ + EPROTONOSUPPORT = 43, /* Protocol not supported */ + ESOCKTNOSUPPORT = 44, /* Socket type not supported */ + ENOTSUP = 45, /* Operation not supported */ + EOPNOTSUPP = ENOTSUP, + EPFNOSUPPORT = 46, /* Protocol family not supported */ + EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ + EADDRINUSE = 48, /* Address already in use */ + EADDRNOTAVAIL = 49, /* Can't assign requested address */ + + /* ipc/network software -- operational errors */ + ENETDOWN = 50, /* Network is down */ + ENETUNREACH = 51, /* Network is unreachable */ + ENETRESET = 52, /* Network dropped connection on reset */ + ECONNABORTED = 53, /* Software caused connection abort */ + ECONNRESET = 54, /* Connection reset by peer */ + ENOBUFS = 55, /* No buffer space available */ + EISCONN = 56, /* Socket is already connected */ + ENOTCONN = 57, /* Socket is not connected */ + ESHUTDOWN = 58, /* Can't send after socket shutdown */ + ETOOMANYREFS = 59, /* Too many references: can't splice */ + ETIMEDOUT = 60, /* Operation timed out */ + ECONNREFUSED = 61, /* Connection refused */ + + ELOOP = 62, /* Too many levels of symbolic links */ + ENAMETOOLONG = 63, /* File name too long */ + + /* should be rearranged */ + EHOSTDOWN = 64, /* Host is down */ + EHOSTUNREACH = 65, /* No route to host */ + ENOTEMPTY = 66, /* Directory not empty */ + + /* quotas & mush */ + EPROCLIM = 67, /* Too many processes */ + EUSERS = 68, /* Too many users */ + EDQUOT = 69, /* Disc quota exceeded */ + + /* Network File System */ + ESTALE = 70, /* Stale NFS file handle */ + EREMOTE = 71, /* Too many levels of remote in path */ + EBADRPC = 72, /* RPC struct is bad */ + ERPCMISMATCH = 73, /* RPC version wrong */ + EPROGUNAVAIL = 74, /* RPC prog. not avail */ + EPROGMISMATCH = 75, /* Program version wrong */ + EPROCUNAVAIL = 76, /* Bad procedure for program */ + + ENOLCK = 77, /* No locks available */ + ENOSYS = 78, /* Function not implemented */ + + EFTYPE = 79, /* Inappropriate file type or format */ + EAUTH = 80, /* Authentication error */ + ENEEDAUTH = 81, /* Need authenticator */ + + /* Intelligent device errors */ + EPWROFF = 82, /* Device power is off */ + EDEVERR = 83, /* Device error, e.g. paper out */ + EOVERFLOW = 84, /* Value too large to be stored in data type */ + + /* Program loading errors */ + EBADEXEC = 85, /* Bad executable */ + EBADARCH = 86, /* Bad CPU type in executable */ + ESHLIBVERS = 87, /* Shared library version mismatch */ + EBADMACHO = 88, /* Malformed Macho file */ + + ECANCELED = 89, /* Operation canceled */ + + EIDRM = 90, /* Identifier removed */ + ENOMSG = 91, /* No message of desired type */ + EILSEQ = 92, /* Illegal byte sequence */ + ENOATTR = 93, /* Attribute not found */ + + EBADMSG = 94, /* Bad message */ + EMULTIHOP = 95, /* Reserved */ + ENODATA = 96, /* No message available on STREAM */ + ENOLINK = 97, /* Reserved */ + ENOSR = 98, /* No STREAM resources */ + ENOSTR = 99, /* Not a STREAM */ + EPROTO = 100, /* Protocol error */ + ETIME = 101, /* STREAM ioctl timeout */ + + ENOPOLICY = 103, /* No such policy registered */ + + ENOTRECOVERABLE = 104, /* State not recoverable */ + EOWNERDEAD = 105, /* Previous owner died */ + + EQFULL = 106, /* Interface output queue is full */ + ELAST = 106, /* Must be equal largest errno */ +} + +EPERM :: _Platform_Error.EPERM +ENOENT :: _Platform_Error.ENOENT +ESRCH :: _Platform_Error.ESRCH +EINTR :: _Platform_Error.EINTR +EIO :: _Platform_Error.EIO +ENXIO :: _Platform_Error.ENXIO +E2BIG :: _Platform_Error.E2BIG +ENOEXEC :: _Platform_Error.ENOEXEC +EBADF :: _Platform_Error.EBADF +ECHILD :: _Platform_Error.ECHILD +EDEADLK :: _Platform_Error.EDEADLK +ENOMEM :: _Platform_Error.ENOMEM +EACCES :: _Platform_Error.EACCES +EFAULT :: _Platform_Error.EFAULT +ENOTBLK :: _Platform_Error.ENOTBLK +EBUSY :: _Platform_Error.EBUSY +EEXIST :: _Platform_Error.EEXIST +EXDEV :: _Platform_Error.EXDEV +ENODEV :: _Platform_Error.ENODEV +ENOTDIR :: _Platform_Error.ENOTDIR +EISDIR :: _Platform_Error.EISDIR +EINVAL :: _Platform_Error.EINVAL +ENFILE :: _Platform_Error.ENFILE +EMFILE :: _Platform_Error.EMFILE +ENOTTY :: _Platform_Error.ENOTTY +ETXTBSY :: _Platform_Error.ETXTBSY +EFBIG :: _Platform_Error.EFBIG +ENOSPC :: _Platform_Error.ENOSPC +ESPIPE :: _Platform_Error.ESPIPE +EROFS :: _Platform_Error.EROFS +EMLINK :: _Platform_Error.EMLINK +EPIPE :: _Platform_Error.EPIPE /* math software */ -EDOM: Errno : 33 /* Numerical argument out of domain */ -ERANGE: Errno : 34 /* Result too large */ +EDOM :: _Platform_Error.EDOM +ERANGE :: _Platform_Error.ERANGE /* non-blocking and interrupt i/o */ -EAGAIN: Errno : 35 /* Resource temporarily unavailable */ -EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ -EINPROGRESS: Errno : 36 /* Operation now in progress */ -EALREADY: Errno : 37 /* Operation already in progress */ +EAGAIN :: _Platform_Error.EAGAIN +EWOULDBLOCK :: _Platform_Error.EWOULDBLOCK +EINPROGRESS :: _Platform_Error.EINPROGRESS +EALREADY :: _Platform_Error.EALREADY /* ipc/network software -- argument errors */ -ENOTSOCK: Errno : 38 /* Socket operation on non-socket */ -EDESTADDRREQ: Errno : 39 /* Destination address required */ -EMSGSIZE: Errno : 40 /* Message too long */ -EPROTOTYPE: Errno : 41 /* Protocol wrong type for socket */ -ENOPROTOOPT: Errno : 42 /* Protocol not available */ -EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ -ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ -ENOTSUP: Errno : 45 /* Operation not supported */ -EOPNOTSUPP:: ENOTSUP -EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ -EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ -EADDRINUSE: Errno : 48 /* Address already in use */ -EADDRNOTAVAIL: Errno : 49 /* Can't assign requested address */ +ENOTSOCK :: _Platform_Error.ENOTSOCK +EDESTADDRREQ :: _Platform_Error.EDESTADDRREQ +EMSGSIZE :: _Platform_Error.EMSGSIZE +EPROTOTYPE :: _Platform_Error.EPROTOTYPE +ENOPROTOOPT :: _Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: _Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: _Platform_Error.ESOCKTNOSUPPORT +ENOTSUP :: _Platform_Error.ENOTSUP +EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: _Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: _Platform_Error.EAFNOSUPPORT +EADDRINUSE :: _Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: _Platform_Error.EADDRNOTAVAIL /* ipc/network software -- operational errors */ -ENETDOWN: Errno : 50 /* Network is down */ -ENETUNREACH: Errno : 51 /* Network is unreachable */ -ENETRESET: Errno : 52 /* Network dropped connection on reset */ -ECONNABORTED: Errno : 53 /* Software caused connection abort */ -ECONNRESET: Errno : 54 /* Connection reset by peer */ -ENOBUFS: Errno : 55 /* No buffer space available */ -EISCONN: Errno : 56 /* Socket is already connected */ -ENOTCONN: Errno : 57 /* Socket is not connected */ -ESHUTDOWN: Errno : 58 /* Can't send after socket shutdown */ -ETOOMANYREFS: Errno : 59 /* Too many references: can't splice */ -ETIMEDOUT: Errno : 60 /* Operation timed out */ -ECONNREFUSED: Errno : 61 /* Connection refused */ +ENETDOWN :: _Platform_Error.ENETDOWN +ENETUNREACH :: _Platform_Error.ENETUNREACH +ENETRESET :: _Platform_Error.ENETRESET +ECONNABORTED :: _Platform_Error.ECONNABORTED +ECONNRESET :: _Platform_Error.ECONNRESET +ENOBUFS :: _Platform_Error.ENOBUFS +EISCONN :: _Platform_Error.EISCONN +ENOTCONN :: _Platform_Error.ENOTCONN +ESHUTDOWN :: _Platform_Error.ESHUTDOWN +ETOOMANYREFS :: _Platform_Error.ETOOMANYREFS +ETIMEDOUT :: _Platform_Error.ETIMEDOUT +ECONNREFUSED :: _Platform_Error.ECONNREFUSED -ELOOP: Errno : 62 /* Too many levels of symbolic links */ -ENAMETOOLONG: Errno : 63 /* File name too long */ +ELOOP :: _Platform_Error.ELOOP +ENAMETOOLONG :: _Platform_Error.ENAMETOOLONG /* should be rearranged */ -EHOSTDOWN: Errno : 64 /* Host is down */ -EHOSTUNREACH: Errno : 65 /* No route to host */ -ENOTEMPTY: Errno : 66 /* Directory not empty */ +EHOSTDOWN :: _Platform_Error.EHOSTDOWN +EHOSTUNREACH :: _Platform_Error.EHOSTUNREACH +ENOTEMPTY :: _Platform_Error.ENOTEMPTY /* quotas & mush */ -EPROCLIM: Errno : 67 /* Too many processes */ -EUSERS: Errno : 68 /* Too many users */ -EDQUOT: Errno : 69 /* Disc quota exceeded */ +EPROCLIM :: _Platform_Error.EPROCLIM +EUSERS :: _Platform_Error.EUSERS +EDQUOT :: _Platform_Error.EDQUOT /* Network File System */ -ESTALE: Errno : 70 /* Stale NFS file handle */ -EREMOTE: Errno : 71 /* Too many levels of remote in path */ -EBADRPC: Errno : 72 /* RPC struct is bad */ -ERPCMISMATCH: Errno : 73 /* RPC version wrong */ -EPROGUNAVAIL: Errno : 74 /* RPC prog. not avail */ -EPROGMISMATCH: Errno : 75 /* Program version wrong */ -EPROCUNAVAIL: Errno : 76 /* Bad procedure for program */ +ESTALE :: _Platform_Error.ESTALE +EREMOTE :: _Platform_Error.EREMOTE +EBADRPC :: _Platform_Error.EBADRPC +ERPCMISMATCH :: _Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: _Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: _Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: _Platform_Error.EPROCUNAVAIL -ENOLCK: Errno : 77 /* No locks available */ -ENOSYS: Errno : 78 /* Function not implemented */ +ENOLCK :: _Platform_Error.ENOLCK +ENOSYS :: _Platform_Error.ENOSYS -EFTYPE: Errno : 79 /* Inappropriate file type or format */ -EAUTH: Errno : 80 /* Authentication error */ -ENEEDAUTH: Errno : 81 /* Need authenticator */ +EFTYPE :: _Platform_Error.EFTYPE +EAUTH :: _Platform_Error.EAUTH +ENEEDAUTH :: _Platform_Error.ENEEDAUTH /* Intelligent device errors */ -EPWROFF: Errno : 82 /* Device power is off */ -EDEVERR: Errno : 83 /* Device error, e.g. paper out */ -EOVERFLOW: Errno : 84 /* Value too large to be stored in data type */ +EPWROFF :: _Platform_Error.EPWROFF +EDEVERR :: _Platform_Error.EDEVERR +EOVERFLOW :: _Platform_Error.EOVERFLOW /* Program loading errors */ -EBADEXEC: Errno : 85 /* Bad executable */ -EBADARCH: Errno : 86 /* Bad CPU type in executable */ -ESHLIBVERS: Errno : 87 /* Shared library version mismatch */ -EBADMACHO: Errno : 88 /* Malformed Macho file */ +EBADEXEC :: _Platform_Error.EBADEXEC +EBADARCH :: _Platform_Error.EBADARCH +ESHLIBVERS :: _Platform_Error.ESHLIBVERS +EBADMACHO :: _Platform_Error.EBADMACHO -ECANCELED: Errno : 89 /* Operation canceled */ +ECANCELED :: _Platform_Error.ECANCELED -EIDRM: Errno : 90 /* Identifier removed */ -ENOMSG: Errno : 91 /* No message of desired type */ -EILSEQ: Errno : 92 /* Illegal byte sequence */ -ENOATTR: Errno : 93 /* Attribute not found */ +EIDRM :: _Platform_Error.EIDRM +ENOMSG :: _Platform_Error.ENOMSG +EILSEQ :: _Platform_Error.EILSEQ +ENOATTR :: _Platform_Error.ENOATTR -EBADMSG: Errno : 94 /* Bad message */ -EMULTIHOP: Errno : 95 /* Reserved */ -ENODATA: Errno : 96 /* No message available on STREAM */ -ENOLINK: Errno : 97 /* Reserved */ -ENOSR: Errno : 98 /* No STREAM resources */ -ENOSTR: Errno : 99 /* Not a STREAM */ -EPROTO: Errno : 100 /* Protocol error */ -ETIME: Errno : 101 /* STREAM ioctl timeout */ +EBADMSG :: _Platform_Error.EBADMSG +EMULTIHOP :: _Platform_Error.EMULTIHOP +ENODATA :: _Platform_Error.ENODATA +ENOLINK :: _Platform_Error.ENOLINK +ENOSR :: _Platform_Error.ENOSR +ENOSTR :: _Platform_Error.ENOSTR +EPROTO :: _Platform_Error.EPROTO +ETIME :: _Platform_Error.ETIME -ENOPOLICY: Errno : 103 /* No such policy registered */ +ENOPOLICY :: _Platform_Error.ENOPOLICY -ENOTRECOVERABLE: Errno : 104 /* State not recoverable */ -EOWNERDEAD: Errno : 105 /* Previous owner died */ +ENOTRECOVERABLE :: _Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: _Platform_Error.EOWNERDEAD + +EQFULL :: _Platform_Error.EQFULL +ELAST :: _Platform_Error.ELAST -EQFULL: Errno : 106 /* Interface output queue is full */ -ELAST: Errno : 106 /* Must be equal largest errno */ O_RDONLY :: 0x0000 O_WRONLY :: 0x0001 @@ -424,13 +562,13 @@ S_ISUID :: 0o4000 // Set user id on execution S_ISGID :: 0o2000 // Set group id on execution S_ISVTX :: 0o1000 // Directory restrcted delete -S_ISLNK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFSOCK } R_OK :: 4 // Test for read permission W_OK :: 2 // Test for write permission @@ -477,7 +615,11 @@ foreign libc { @(link_name="calloc") _unix_calloc :: proc(num, size: int) -> rawptr --- @(link_name="free") _unix_free :: proc(ptr: rawptr) --- @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="unsetenv") _unix_unsetenv :: proc(cstring) -> c.int --- + @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(buf: cstring, mode: u16) -> c.int --- @@ -520,16 +662,19 @@ foreign dl { @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } -get_last_error :: proc "contextless" () -> int { - return int(__error()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) } +@(require_results) get_last_error_string :: proc() -> string { - return cast(string)_darwin_string_error(cast(c.int)get_last_error()) + return string(_darwin_string_error(__error()^)) } -open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (handle: Handle, err: Error) { isDir := is_dir_path(path) flags := flags if isDir { @@ -542,9 +687,10 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, i32(flags), u16(mode)) - if handle == -1 { - return INVALID_HANDLE, cast(Errno)get_last_error() + handle = _unix_open(cstr, i32(flags), u16(mode)) + if handle == INVALID_HANDLE { + err = get_last_error() + return } /* @@ -552,22 +698,22 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno should not happen if the handle is a directory */ if mode != 0 && !isDir { - err := fchmod(handle, cast(u16)mode) - if err != 0 { + err = fchmod(handle, cast(u16)mode) + if err != nil { _unix_close(handle) - return INVALID_HANDLE, err + handle = INVALID_HANDLE } } - return handle, 0 + return } -fchmod :: proc(fd: Handle, mode: u16) -> Errno { - return cast(Errno)_unix_fchmod(fd, mode) +fchmod :: proc(fd: Handle, mode: u16) -> Error { + return cast(Platform_Error)_unix_fchmod(fd, mode) } -close :: proc(fd: Handle) -> Errno { - return cast(Errno)_unix_close(fd) +close :: proc(fd: Handle) -> Error { + return cast(Platform_Error)_unix_close(fd) } // If you read or write more than `SSIZE_MAX` bytes, most darwin implementations will return `EINVAL` @@ -580,73 +726,74 @@ close :: proc(fd: Handle) -> Errno { @(private) MAX_RW :: 1 << 30 -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(c.size_t(len(data)), MAX_RW) bytes_written := _unix_write(fd, raw_data(data), to_write) if bytes_written < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_written, ERROR_NONE + return bytes_written, nil } -read :: proc(fd: Handle, data: []u8) -> (int, Errno) { +read :: proc(fd: Handle, data: []u8) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_read := min(c.size_t(len(data)), MAX_RW) bytes_read := _unix_read(fd, raw_data(data), to_read) if bytes_read < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_read, ERROR_NONE + return bytes_read, nil } -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_read := min(c.size_t(len(data)), MAX_RW) bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) if bytes_read < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_read, ERROR_NONE + return bytes_read, nil } -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(c.size_t(len(data)), MAX_RW) bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) if bytes_written < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_written, ERROR_NONE + return bytes_written, nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { assert(fd != -1) final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) if final_offset == -1 { - return 0, 1 + return 0, get_last_error() } - return final_offset, 0 + return final_offset, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { prev, _ := seek(fd, 0, SEEK_CUR) size, err := seek(fd, 0, SEEK_END) seek(fd, prev, SEEK_SET) @@ -660,69 +807,70 @@ stdin: Handle = 0 // get_std_handle(win32.STD_INPUT_HANDLE); stdout: Handle = 1 // get_std_handle(win32.STD_OUTPUT_HANDLE); stderr: Handle = 2 // get_std_handle(win32.STD_ERROR_HANDLE); -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { - s, err := _stat(name) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -731,6 +879,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +@(require_results) exists :: proc(path: string) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cpath := strings.clone_to_cstring(path, context.temp_allocator) @@ -745,85 +894,84 @@ rename :: proc(old: string, new: string) -> bool { return _unix_rename(old_cstr, new_cstr) != -1 } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_remove(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) s: OS_Stat result := _unix_stat(cstr, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) s: OS_Stat result := _unix_lstat(cstr, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { s: OS_Stat result := _unix_fstat(fd, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE if result == nil { end_of_stream = true @@ -834,8 +982,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -845,30 +993,27 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { // NOTE(laleksic, 2021-01-21): Any cleaner way to resize the slice? bufsz *= 2 delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } } -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { buf: [DARWIN_MAXPATHLEN]byte - _, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) - if err != ERROR_NONE { - return "", err - } - - path := strings.clone_from_cstring(cstring(&buf[0])) - return path, err + _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return + return strings.clone_from_cstring(cstring(&buf[0])) } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -879,14 +1024,14 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } defer _unix_free(path_ptr) path_cstr := cast(cstring)path_ptr path = strings.clone(string(path_cstr)) - return path, ERROR_NONE + return path, nil } access :: proc(path: string, mask: int) -> bool { @@ -895,10 +1040,11 @@ access :: proc(path: string, mask: int) -> bool { return _unix_access(cstr, c.int(mask)) == 0 } -flush :: proc(fd: Handle) -> Errno { - return cast(Errno)_unix_fsync(fd) +flush :: proc(fd: Handle) -> Error { + return cast(Platform_Error)_unix_fsync(fd) } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) @@ -909,11 +1055,34 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +set_env :: proc(key, value: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + key_cstring := strings.clone_to_cstring(key, context.temp_allocator) + value_cstring := strings.clone_to_cstring(value, context.temp_allocator) + res := _unix_setenv(key_cstring, value_cstring, 1) + if res < 0 { + return get_last_error() + } + return nil +} + +unset_env :: proc(key: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + s := strings.clone_to_cstring(key, context.temp_allocator) + res := _unix_unsetenv(s) + if res < 0 { + return get_last_error() + } + return nil +} + +@(require_results) get_current_directory :: proc() -> string { page_size := get_page_size() // NOTE(tetra): See note in os_linux.odin/get_current_directory. buf := make([dynamic]u8, page_size) @@ -922,7 +1091,7 @@ get_current_directory :: proc() -> string { if cwd != nil { return string(cwd) } - if Errno(get_last_error()) != ERANGE { + if get_last_error() != ERANGE { delete(buf) return "" } @@ -931,24 +1100,24 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -make_directory :: proc(path: string, mode: u16 = 0o775) -> Errno { +make_directory :: proc(path: string, mode: u16 = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_mkdir(path_cstr, mode) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -956,6 +1125,7 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(i32(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { tid: u64 // NOTE(Oskar): available from OSX 10.6 and iOS 3.2. @@ -966,12 +1136,14 @@ current_thread_id :: proc "contextless" () -> int { return int(tid) } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) handle := _unix_dlopen(cstr, flags) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -987,7 +1159,8 @@ dlerror :: proc() -> string { return string(_unix_dlerror()) } -get_page_size :: proc "contextless" () -> int { +@(require_results) +get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @static page_size := -1 @@ -999,7 +1172,7 @@ get_page_size :: proc "contextless" () -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) @@ -1012,6 +1185,7 @@ _processor_core_count :: proc() -> int { return 1 } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for _, i in res { @@ -1020,106 +1194,108 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) { +@(require_results) +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { result := _unix_socket(domain, type, protocol) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return Socket(result), ERROR_NONE + return Socket(result), nil } -connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { +@(require_results) +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { result := _unix_connect(int(sd), addr, len) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { result := _unix_bind(int(sd), addr, len) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) { +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { result := _unix_accept(int(sd), rawptr(addr), len) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return Socket(result), ERROR_NONE + return Socket(result), nil } -listen :: proc(sd: Socket, backlog: int) -> (Errno) { +listen :: proc(sd: Socket, backlog: int) -> Error { result := _unix_listen(int(sd), backlog) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) { +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { result := _unix_setsockopt(int(sd), level, optname, optval, optlen) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Errno { +getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { result := _unix_getsockopt(int(sd), level, optname, optval, optlen) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) { +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { result := _unix_recv(int(sd), raw_data(data), len(data), flags) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) { +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { result := _unix_send(int(sd), raw_data(data), len(data), 0) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -shutdown :: proc(sd: Socket, how: int) -> (Errno) { +shutdown :: proc(sd: Socket, how: int) -> Error { result := _unix_shutdown(int(sd), how) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { result := _unix__fcntl(Handle(fd), c.int(cmd), uintptr(arg)) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return int(result), ERROR_NONE + return int(result), nil } diff --git a/core/os/os_essence.odin b/core/os/os_essence.odin index e4281f6c9..75c4c1156 100644 --- a/core/os/os_essence.odin +++ b/core/os/os_essence.odin @@ -2,54 +2,59 @@ package os import "core:sys/es" -Handle :: distinct int; -Errno :: distinct int; +Handle :: distinct int +_Platform_Error :: enum i32 {NONE} -ERROR_NONE :: (Errno) (es.SUCCESS); +// ERROR_NONE :: Error(es.SUCCESS) -O_RDONLY :: 0x1; -O_WRONLY :: 0x2; -O_CREATE :: 0x4; -O_TRUNC :: 0x8; +O_RDONLY :: 0x1 +O_WRONLY :: 0x2 +O_CREATE :: 0x4 +O_TRUNC :: 0x8 -stderr : Handle = 0; +stderr : Handle = 0 current_thread_id :: proc "contextless" () -> int { - return (int) (es.ThreadGetID(es.CURRENT_THREAD)); + return (int) (es.ThreadGetID(es.CURRENT_THREAD)) } heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - return es.HeapAllocate(size, zero_memory); + return es.HeapAllocate(size, zero_memory) } heap_free :: proc(ptr: rawptr) { - es.HeapFree(ptr); + es.HeapFree(ptr) } heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - return es.HeapReallocate(ptr, new_size, false); + return es.HeapReallocate(ptr, new_size, false) } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { - return (Handle) (0), (Errno) (1); +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + return (Handle) (0), (Error) (1) } -close :: proc(fd: Handle) -> Errno { - return (Errno) (1); +close :: proc(fd: Handle) -> Error { + return (Error) (1) } -file_size :: proc(fd: Handle) -> (i64, Errno) { - return (i64) (0), (Errno) (1); +file_size :: proc(fd: Handle) -> (i64, Error) { + return (i64) (0), (Error) (1) } -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - return (int) (0), (Errno) (1); +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + return (int) (0), (Error) (1) } -write :: proc(fd: Handle, data: []u8) -> (int, Errno) { - return (int) (0), (Errno) (1); +write :: proc(fd: Handle, data: []u8) -> (int, Error) { + return (int) (0), (Error) (1) } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { - return (i64) (0), (Errno) (1); +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + return (i64) (0), (Error) (1) } + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil +} \ No newline at end of file diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 2a98a521f..c7955368e 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -9,105 +9,200 @@ import "core:c" Handle :: distinct i32 File_Time :: distinct u64 -Errno :: distinct i32 INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno : 0 -EPERM: Errno : 1 -ENOENT: Errno : 2 -ESRCH: Errno : 3 -EINTR: Errno : 4 -EIO: Errno : 5 -ENXIO: Errno : 6 -E2BIG: Errno : 7 -ENOEXEC: Errno : 8 -EBADF: Errno : 9 -ECHILD: Errno : 10 -EBEADLK: Errno : 11 -ENOMEM: Errno : 12 -EACCESS: Errno : 13 -EFAULT: Errno : 14 -ENOTBLK: Errno : 15 -EBUSY: Errno : 16 -EEXIST: Errno : 17 -EXDEV: Errno : 18 -ENODEV: Errno : 19 -ENOTDIR: Errno : 20 -EISDIR: Errno : 21 -EINVAL: Errno : 22 -ENFILE: Errno : 23 -EMFILE: Errno : 24 -ENOTTY: Errno : 25 -ETXTBSY: Errno : 26 -EFBIG: Errno : 27 -ENOSPC: Errno : 28 -ESPIPE: Errno : 29 -EROFS: Errno : 30 -EMLINK: Errno : 31 -EPIPE: Errno : 32 -EDOM: Errno : 33 -ERANGE: Errno : 34 /* Result too large */ -EAGAIN: Errno : 35 -EINPROGRESS: Errno : 36 -EALREADY: Errno : 37 -ENOTSOCK: Errno : 38 -EDESTADDRREQ: Errno : 39 -EMSGSIZE: Errno : 40 -EPROTOTYPE: Errno : 41 -ENOPROTOOPT: Errno : 42 -EPROTONOSUPPORT: Errno : 43 -ESOCKTNOSUPPORT: Errno : 44 -EOPNOTSUPP: Errno : 45 -EPFNOSUPPORT: Errno : 46 -EAFNOSUPPORT: Errno : 47 -EADDRINUSE: Errno : 48 -EADDRNOTAVAIL: Errno : 49 -ENETDOWN: Errno : 50 -ENETUNREACH: Errno : 51 -ENETRESET: Errno : 52 -ECONNABORTED: Errno : 53 -ECONNRESET: Errno : 54 -ENOBUFS: Errno : 55 -EISCONN: Errno : 56 -ENOTCONN: Errno : 57 -ESHUTDOWN: Errno : 58 -ETIMEDOUT: Errno : 60 -ECONNREFUSED: Errno : 61 -ELOOP: Errno : 62 -ENAMETOOLING: Errno : 63 -EHOSTDOWN: Errno : 64 -EHOSTUNREACH: Errno : 65 -ENOTEMPTY: Errno : 66 -EPROCLIM: Errno : 67 -EUSERS: Errno : 68 -EDQUOT: Errno : 69 -ESTALE: Errno : 70 -EBADRPC: Errno : 72 -ERPCMISMATCH: Errno : 73 -EPROGUNAVAIL: Errno : 74 -EPROGMISMATCH: Errno : 75 -EPROCUNAVAIL: Errno : 76 -ENOLCK: Errno : 77 -ENOSYS: Errno : 78 -EFTYPE: Errno : 79 -EAUTH: Errno : 80 -ENEEDAUTH: Errno : 81 -EIDRM: Errno : 82 -ENOMSG: Errno : 83 -EOVERFLOW: Errno : 84 -ECANCELED: Errno : 85 -EILSEQ: Errno : 86 -ENOATTR: Errno : 87 -EDOOFUS: Errno : 88 -EBADMSG: Errno : 89 -EMULTIHOP: Errno : 90 -ENOLINK: Errno : 91 -EPROTO: Errno : 92 -ENOTCAPABLE: Errno : 93 -ECAPMODE: Errno : 94 -ENOTRECOVERABLE: Errno : 95 -EOWNERDEAD: Errno : 96 +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, + ENOENT = 2, + ESRCH = 3, + EINTR = 4, + EIO = 5, + ENXIO = 6, + E2BIG = 7, + ENOEXEC = 8, + EBADF = 9, + ECHILD = 10, + EBEADLK = 11, + ENOMEM = 12, + EACCESS = 13, + EFAULT = 14, + ENOTBLK = 15, + EBUSY = 16, + EEXIST = 17, + EXDEV = 18, + ENODEV = 19, + ENOTDIR = 20, + EISDIR = 21, + EINVAL = 22, + ENFILE = 23, + EMFILE = 24, + ENOTTY = 25, + ETXTBSY = 26, + EFBIG = 27, + ENOSPC = 28, + ESPIPE = 29, + EROFS = 30, + EMLINK = 31, + EPIPE = 32, + EDOM = 33, + ERANGE = 34, /* Result too large */ + EAGAIN = 35, + EINPROGRESS = 36, + EALREADY = 37, + ENOTSOCK = 38, + EDESTADDRREQ = 39, + EMSGSIZE = 40, + EPROTOTYPE = 41, + ENOPROTOOPT = 42, + EPROTONOSUPPORT = 43, + ESOCKTNOSUPPORT = 44, + EOPNOTSUPP = 45, + EPFNOSUPPORT = 46, + EAFNOSUPPORT = 47, + EADDRINUSE = 48, + EADDRNOTAVAIL = 49, + ENETDOWN = 50, + ENETUNREACH = 51, + ENETRESET = 52, + ECONNABORTED = 53, + ECONNRESET = 54, + ENOBUFS = 55, + EISCONN = 56, + ENOTCONN = 57, + ESHUTDOWN = 58, + ETIMEDOUT = 60, + ECONNREFUSED = 61, + ELOOP = 62, + ENAMETOOLING = 63, + EHOSTDOWN = 64, + EHOSTUNREACH = 65, + ENOTEMPTY = 66, + EPROCLIM = 67, + EUSERS = 68, + EDQUOT = 69, + ESTALE = 70, + EBADRPC = 72, + ERPCMISMATCH = 73, + EPROGUNAVAIL = 74, + EPROGMISMATCH = 75, + EPROCUNAVAIL = 76, + ENOLCK = 77, + ENOSYS = 78, + EFTYPE = 79, + EAUTH = 80, + ENEEDAUTH = 81, + EIDRM = 82, + ENOMSG = 83, + EOVERFLOW = 84, + ECANCELED = 85, + EILSEQ = 86, + ENOATTR = 87, + EDOOFUS = 88, + EBADMSG = 89, + EMULTIHOP = 90, + ENOLINK = 91, + EPROTO = 92, + ENOTCAPABLE = 93, + ECAPMODE = 94, + ENOTRECOVERABLE = 95, + EOWNERDEAD = 96, +} +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +E2BIG :: Platform_Error.E2BIG +ENOEXEC :: Platform_Error.ENOEXEC +EBADF :: Platform_Error.EBADF +ECHILD :: Platform_Error.ECHILD +EBEADLK :: Platform_Error.EBEADLK +ENOMEM :: Platform_Error.ENOMEM +EACCESS :: Platform_Error.EACCESS +EFAULT :: Platform_Error.EFAULT +ENOTBLK :: Platform_Error.ENOTBLK +EBUSY :: Platform_Error.EBUSY +EEXIST :: Platform_Error.EEXIST +EXDEV :: Platform_Error.EXDEV +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ENOTTY :: Platform_Error.ENOTTY +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EMLINK :: Platform_Error.EMLINK +EPIPE :: Platform_Error.EPIPE +EDOM :: Platform_Error.EDOM +ERANGE :: Platform_Error.ERANGE +EAGAIN :: Platform_Error.EAGAIN +EINPROGRESS :: Platform_Error.EINPROGRESS +EALREADY :: Platform_Error.EALREADY +ENOTSOCK :: Platform_Error.ENOTSOCK +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ +EMSGSIZE :: Platform_Error.EMSGSIZE +EPROTOTYPE :: Platform_Error.EPROTOTYPE +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT +EADDRINUSE :: Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL +ENETDOWN :: Platform_Error.ENETDOWN +ENETUNREACH :: Platform_Error.ENETUNREACH +ENETRESET :: Platform_Error.ENETRESET +ECONNABORTED :: Platform_Error.ECONNABORTED +ECONNRESET :: Platform_Error.ECONNRESET +ENOBUFS :: Platform_Error.ENOBUFS +EISCONN :: Platform_Error.EISCONN +ENOTCONN :: Platform_Error.ENOTCONN +ESHUTDOWN :: Platform_Error.ESHUTDOWN +ETIMEDOUT :: Platform_Error.ETIMEDOUT +ECONNREFUSED :: Platform_Error.ECONNREFUSED +ELOOP :: Platform_Error.ELOOP +ENAMETOOLING :: Platform_Error.ENAMETOOLING +EHOSTDOWN :: Platform_Error.EHOSTDOWN +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH +ENOTEMPTY :: Platform_Error.ENOTEMPTY +EPROCLIM :: Platform_Error.EPROCLIM +EUSERS :: Platform_Error.EUSERS +EDQUOT :: Platform_Error.EDQUOT +ESTALE :: Platform_Error.ESTALE +EBADRPC :: Platform_Error.EBADRPC +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL +ENOLCK :: Platform_Error.ENOLCK +ENOSYS :: Platform_Error.ENOSYS +EFTYPE :: Platform_Error.EFTYPE +EAUTH :: Platform_Error.EAUTH +ENEEDAUTH :: Platform_Error.ENEEDAUTH +EIDRM :: Platform_Error.EIDRM +ENOMSG :: Platform_Error.ENOMSG +EOVERFLOW :: Platform_Error.EOVERFLOW +ECANCELED :: Platform_Error.ECANCELED +EILSEQ :: Platform_Error.EILSEQ +ENOATTR :: Platform_Error.ENOATTR +EDOOFUS :: Platform_Error.EDOOFUS +EBADMSG :: Platform_Error.EBADMSG +EMULTIHOP :: Platform_Error.EMULTIHOP +ENOLINK :: Platform_Error.ENOLINK +EPROTO :: Platform_Error.EPROTO +ENOTCAPABLE :: Platform_Error.ENOTCAPABLE +ECAPMODE :: Platform_Error.ECAPMODE +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: Platform_Error.EOWNERDEAD O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 @@ -258,13 +353,13 @@ S_ISGID :: 0o2000 // Set group id on execution S_ISVTX :: 0o1000 // Directory restrcted delete -S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } F_OK :: 0 // Test for file existance X_OK :: 1 // Test for execute permission @@ -274,7 +369,7 @@ R_OK :: 4 // Test for read permission F_KINFO :: 22 foreign libc { - @(link_name="__error") __errno_location :: proc() -> ^c.int --- + @(link_name="__error") __Error_location :: proc() -> ^c.int --- @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @@ -320,30 +415,38 @@ foreign dl { @(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int --- } +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } -get_last_error :: proc "contextless" () -> int { - return int(__errno_location()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__Error_location()^) } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) handle := _unix_open(cstr, c.int(flags), c.int(mode)) if handle == -1 { - return INVALID_HANDLE, Errno(get_last_error()) + return INVALID_HANDLE, get_last_error() } - return handle, ERROR_NONE + return handle, nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { result := _unix_close(fd) if result == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil +} + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil } // If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`. @@ -354,124 +457,148 @@ close :: proc(fd: Handle) -> Errno { @(private) MAX_RW :: 1 << 30 -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { +read :: proc(fd: Handle, data: []byte) -> (int, Error) { to_read := min(c.size_t(len(data)), MAX_RW) bytes_read := _unix_read(fd, &data[0], to_read) if bytes_read == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_read), ERROR_NONE + return int(bytes_read), nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(c.size_t(len(data)), MAX_RW) bytes_written := _unix_write(fd, &data[0], to_write) if bytes_written == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_written), ERROR_NONE + return int(bytes_written), nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = read(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = write(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return res, ERROR_NONE + return res, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return -1, err - } - return s.size, ERROR_NONE +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return } -rename :: proc(old_path, new_path: string) -> Errno { +rename :: proc(old_path, new_path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) res := _unix_rename(old_path_cstr, new_path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_unlink(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_mkdir(path_cstr, mode) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove_directory :: proc(path: string) -> Errno { +remove_directory :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_rmdir(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -490,38 +617,40 @@ stderr: Handle = 2 last_write_time :: proc(fd: Handle) -> File_Time {} last_write_time_by_name :: proc(name: string) -> File_Time {} */ -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return 0, err } modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { s, err := _stat(name) - if err != ERROR_NONE { + if err != nil { return 0, err } modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) s: OS_Stat = --- result := _unix_lstat(cstr, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -529,54 +658,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_lstat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { s: OS_Stat = --- result := _unix_fstat(fd, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE if result == nil { end_of_stream = true @@ -586,8 +714,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -598,20 +726,21 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { bufsz += MAX_PATH delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } - return "", Errno{} + return "", Error{} } -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { // NOTE(Feoramund): The situation isn't ideal, but this was the best way I // could find to implement this. There are a couple outstanding bug reports // regarding the desire to retrieve an absolute path from a handle, but to @@ -626,14 +755,15 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo) if res == -1 { - return "", Errno(get_last_error()) + return "", get_last_error() } path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path)) - return path, ERROR_NONE + return path, nil } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -644,27 +774,28 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } defer _unix_free(path_ptr) path = strings.clone(string(cstring(path_ptr))) - return path, ERROR_NONE + return path, nil } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) result := _unix_access(cstr, c.int(mask)) if result == -1 { - return false, Errno(get_last_error()) + return false, get_last_error() } - return true, ERROR_NONE + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) @@ -676,11 +807,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) get_current_directory :: proc() -> string { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. @@ -692,7 +825,7 @@ get_current_directory :: proc() -> string { if cwd != nil { return string(cwd) } - if Errno(get_last_error()) != ERANGE { + if get_last_error() != ERANGE { delete(buf) return "" } @@ -701,14 +834,14 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -716,16 +849,19 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(c.int(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return cast(int) pthread_getthreadid_np() } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) handle := _unix_dlopen(cstr, c.int(flags)) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -741,7 +877,8 @@ dlerror :: proc() -> string { return string(_unix_dlerror()) } -get_page_size :: proc "contextless" () -> int { +@(require_results) +get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @static page_size := -1 @@ -753,7 +890,7 @@ get_page_size :: proc "contextless" () -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) @@ -767,6 +904,7 @@ _processor_core_count :: proc() -> int { } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { diff --git a/core/os/os_freestanding.odin b/core/os/os_freestanding.odin index 55ce1d12e..c908e3738 100644 --- a/core/os/os_freestanding.odin +++ b/core/os/os_freestanding.odin @@ -1,4 +1,4 @@ -//+freestanding +//+build freestanding package os -#panic("package os does not support a freestanding target") \ No newline at end of file +#panic("package os does not support a freestanding target") diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 06052fc42..7f1ec7089 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -10,16 +10,14 @@ import "core:sys/haiku" Handle :: i32 Pid :: i32 File_Time :: i64 -Errno :: i32 +_Platform_Error :: haiku.Errno MAX_PATH :: haiku.PATH_MAX -ENOSYS :: int(haiku.Errno.POSIX_ERROR_BASE) + 9 +ENOSYS :: _Platform_Error(i32(haiku.Errno.POSIX_ERROR_BASE) + 9) INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno: 0 - stdin: Handle = 0 stdout: Handle = 1 stderr: Handle = 2 @@ -121,7 +119,7 @@ S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK foreign libc { - @(link_name="_errnop") __error :: proc() -> ^c.int --- + @(link_name="_errorp") __error :: proc() -> ^c.int --- @(link_name="fork") _unix_fork :: proc() -> pid_t --- @(link_name="getthrid") _unix_getthrid :: proc() -> int --- @@ -179,38 +177,47 @@ Dirent :: struct { Dir :: distinct rawptr // DIR* +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } -get_last_error :: proc "contextless" () -> int { - return int(__error()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) } -fork :: proc() -> (Pid, Errno) { +@(require_results) +fork :: proc() -> (Pid, Error) { pid := _unix_fork() if pid == -1 { - return Pid(-1), Errno(get_last_error()) + return Pid(-1), get_last_error() } - return Pid(pid), ERROR_NONE + return Pid(pid), nil } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) handle := _unix_open(cstr, c.int(flags), c.int(mode)) if handle == -1 { - return INVALID_HANDLE, Errno(get_last_error()) + return INVALID_HANDLE, get_last_error() } - return handle, ERROR_NONE + return handle, nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { result := _unix_close(fd) if result == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil +} + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil } // In practice a read/write call would probably never read/write these big buffers all at once, @@ -220,47 +227,69 @@ close :: proc(fd: Handle) -> Errno { @(private) MAX_RW :: 1 << 30 -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { +read :: proc(fd: Handle, data: []byte) -> (int, Error) { to_read := min(c.size_t(len(data)), MAX_RW) bytes_read := _unix_read(fd, &data[0], to_read) if bytes_read == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_read), ERROR_NONE + return int(bytes_read), nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(c.size_t(len(data)), MAX_RW) bytes_written := _unix_write(fd, &data[0], to_write) if bytes_written == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_written), ERROR_NONE + return int(bytes_written), nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = read(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = write(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return res, ERROR_NONE + return res, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return -1, err } - return s.size, ERROR_NONE + return s.size, nil } // "Argv" arguments converted to Odin strings args := _alloc_command_line_arguments() +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { @@ -269,8 +298,8 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -278,13 +307,13 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_stat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -292,55 +321,54 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_lstat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized s: OS_Stat = --- res := _unix_fstat(fd, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE if result == nil { end_of_stream = true @@ -350,8 +378,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -361,22 +389,24 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { bufsz += MAX_PATH delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } } -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { - return "", Errno(ENOSYS) +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + return "", Error(ENOSYS) } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -387,26 +417,27 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } defer _unix_free(path_ptr) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) + path_cstr := cstring(path_ptr) + path = strings.clone(string(path_cstr)) - return path, ERROR_NONE + return path, nil } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_access(cstr, c.int(mask)) if res == -1 { - return false, Errno(get_last_error()) + return false, get_last_error() } - return true, ERROR_NONE + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) @@ -417,12 +448,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { info: haiku.system_info haiku.get_system_info(&info) diff --git a/core/os/os_js.odin b/core/os/os_js.odin index ab4a3ec11..eb434c727 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -3,42 +3,45 @@ package os import "base:runtime" +@(require_results) is_path_separator :: proc(c: byte) -> bool { return c == '/' || c == '\\' } -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { unimplemented("core:os procedure not supported on JS target") } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { unimplemented("core:os procedure not supported on JS target") } -flush :: proc(fd: Handle) -> (err: Errno) { +flush :: proc(fd: Handle) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { unimplemented("core:os procedure not supported on JS target") } @(private="file") -read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Errno) { +read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Error) { unimplemented("core:os procedure not supported on JS target") } -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { +read :: proc(fd: Handle, data: []byte) -> (int, Error) { unimplemented("core:os procedure not supported on JS target") } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { unimplemented("core:os procedure not supported on JS target") } -file_size :: proc(fd: Handle) -> (i64, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { unimplemented("core:os procedure not supported on JS target") } @@ -47,38 +50,42 @@ file_size :: proc(fd: Handle) -> (i64, Errno) { MAX_RW :: 1<<30 @(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { unimplemented("core:os procedure not supported on JS target") } @(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { unimplemented("core:os procedure not supported on JS target") } -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { unimplemented("core:os procedure not supported on JS target") } -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) { +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { unimplemented("core:os procedure not supported on JS target") } stdout: Handle = 1 stderr: Handle = 2 +@(require_results) get_std_handle :: proc "contextless" (h: uint) -> Handle { context = runtime.default_context() unimplemented("core:os procedure not supported on JS target") } +@(require_results) exists :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } +@(require_results) is_file :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } +@(require_results) is_dir :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } @@ -86,82 +93,118 @@ is_dir :: proc(path: string) -> bool { // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName //@private cwd_lock := win32.SRWLOCK{} // zero is initialized +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { unimplemented("core:os procedure not supported on JS target") } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -change_directory :: proc(path: string) -> (err: Errno) { +change_directory :: proc(path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) { +make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -remove_directory :: proc(path: string) -> (err: Errno) { +remove_directory :: proc(path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -@(private) +@(private, require_results) is_abs :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } -@(private) +@(private, require_results) fix_long_path :: proc(path: string) -> string { unimplemented("core:os procedure not supported on JS target") } -link :: proc(old_name, new_name: string) -> (err: Errno) { +link :: proc(old_name, new_name: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -unlink :: proc(path: string) -> (err: Errno) { +unlink :: proc(path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -rename :: proc(old_path, new_path: string) -> (err: Errno) { +rename :: proc(old_path, new_path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) { +ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -truncate :: proc(path: string, length: i64) -> (err: Errno) { +truncate :: proc(path: string, length: i64) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -remove :: proc(name: string) -> Errno { +remove :: proc(name: string) -> Error { unimplemented("core:os procedure not supported on JS target") } -pipe :: proc() -> (r, w: Handle, err: Errno) { +@(require_results) +pipe :: proc() -> (r, w: Handle, err: Error) { unimplemented("core:os procedure not supported on JS target") } -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { unimplemented("core:os procedure not supported on JS target") } Handle :: distinct uintptr File_Time :: distinct u64 -Errno :: distinct int + +_Platform_Error :: enum i32 { + NONE = 0, + FILE_NOT_FOUND = 2, + PATH_NOT_FOUND = 3, + ACCESS_DENIED = 5, + INVALID_HANDLE = 6, + NOT_ENOUGH_MEMORY = 8, + NO_MORE_FILES = 18, + HANDLE_EOF = 38, + NETNAME_DELETED = 64, + FILE_EXISTS = 80, + INVALID_PARAMETER = 87, + BROKEN_PIPE = 109, + BUFFER_OVERFLOW = 111, + INSUFFICIENT_BUFFER = 122, + MOD_NOT_FOUND = 126, + PROC_NOT_FOUND = 127, + DIR_NOT_EMPTY = 145, + ALREADY_EXISTS = 183, + ENVVAR_NOT_FOUND = 203, + MORE_DATA = 234, + OPERATION_ABORTED = 995, + IO_PENDING = 997, + NOT_FOUND = 1168, + PRIVILEGE_NOT_HELD = 1314, + WSAEACCES = 10013, + WSAECONNRESET = 10054, + + // Windows reserves errors >= 1<<29 for application use + FILE_IS_PIPE = 1<<29 + 0, + FILE_IS_NOT_DIR = 1<<29 + 1, + NEGATIVE_OFFSET = 1<<29 + 2, +} INVALID_HANDLE :: ~Handle(0) @@ -182,37 +225,34 @@ O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 -ERROR_NONE: Errno : 0 -ERROR_FILE_NOT_FOUND: Errno : 2 -ERROR_PATH_NOT_FOUND: Errno : 3 -ERROR_ACCESS_DENIED: Errno : 5 -ERROR_INVALID_HANDLE: Errno : 6 -ERROR_NOT_ENOUGH_MEMORY: Errno : 8 -ERROR_NO_MORE_FILES: Errno : 18 -ERROR_HANDLE_EOF: Errno : 38 -ERROR_NETNAME_DELETED: Errno : 64 -ERROR_FILE_EXISTS: Errno : 80 -ERROR_INVALID_PARAMETER: Errno : 87 -ERROR_BROKEN_PIPE: Errno : 109 -ERROR_BUFFER_OVERFLOW: Errno : 111 -ERROR_INSUFFICIENT_BUFFER: Errno : 122 -ERROR_MOD_NOT_FOUND: Errno : 126 -ERROR_PROC_NOT_FOUND: Errno : 127 -ERROR_DIR_NOT_EMPTY: Errno : 145 -ERROR_ALREADY_EXISTS: Errno : 183 -ERROR_ENVVAR_NOT_FOUND: Errno : 203 -ERROR_MORE_DATA: Errno : 234 -ERROR_OPERATION_ABORTED: Errno : 995 -ERROR_IO_PENDING: Errno : 997 -ERROR_NOT_FOUND: Errno : 1168 -ERROR_PRIVILEGE_NOT_HELD: Errno : 1314 -WSAEACCES: Errno : 10013 -WSAECONNRESET: Errno : 10054 +ERROR_FILE_NOT_FOUND :: Platform_Error.FILE_NOT_FOUND +ERROR_PATH_NOT_FOUND :: Platform_Error.PATH_NOT_FOUND +ERROR_ACCESS_DENIED :: Platform_Error.ACCESS_DENIED +ERROR_INVALID_HANDLE :: Platform_Error.INVALID_HANDLE +ERROR_NOT_ENOUGH_MEMORY :: Platform_Error.NOT_ENOUGH_MEMORY +ERROR_NO_MORE_FILES :: Platform_Error.NO_MORE_FILES +ERROR_HANDLE_EOF :: Platform_Error.HANDLE_EOF +ERROR_NETNAME_DELETED :: Platform_Error.NETNAME_DELETED +ERROR_FILE_EXISTS :: Platform_Error.FILE_EXISTS +ERROR_INVALID_PARAMETER :: Platform_Error.INVALID_PARAMETER +ERROR_BROKEN_PIPE :: Platform_Error.BROKEN_PIPE +ERROR_BUFFER_OVERFLOW :: Platform_Error.BUFFER_OVERFLOW +ERROR_INSUFFICIENT_BUFFER :: Platform_Error.INSUFFICIENT_BUFFER +ERROR_MOD_NOT_FOUND :: Platform_Error.MOD_NOT_FOUND +ERROR_PROC_NOT_FOUND :: Platform_Error.PROC_NOT_FOUND +ERROR_DIR_NOT_EMPTY :: Platform_Error.DIR_NOT_EMPTY +ERROR_ALREADY_EXISTS :: Platform_Error.ALREADY_EXISTS +ERROR_ENVVAR_NOT_FOUND :: Platform_Error.ENVVAR_NOT_FOUND +ERROR_MORE_DATA :: Platform_Error.MORE_DATA +ERROR_OPERATION_ABORTED :: Platform_Error.OPERATION_ABORTED +ERROR_IO_PENDING :: Platform_Error.IO_PENDING +ERROR_NOT_FOUND :: Platform_Error.NOT_FOUND +ERROR_PRIVILEGE_NOT_HELD :: Platform_Error.PRIVILEGE_NOT_HELD +WSAEACCES :: Platform_Error.WSAEACCES +WSAECONNRESET :: Platform_Error.WSAECONNRESET -// Windows reserves errors >= 1<<29 for application use -ERROR_FILE_IS_PIPE: Errno : 1<<29 + 0 -ERROR_FILE_IS_NOT_DIR: Errno : 1<<29 + 1 -ERROR_NEGATIVE_OFFSET: Errno : 1<<29 + 2 +ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe +ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir // "Argv" arguments converted to Odin strings args := _alloc_command_line_arguments() @@ -221,20 +261,23 @@ args := _alloc_command_line_arguments() -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { unimplemented("core:os procedure not supported on JS target") } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { unimplemented("core:os procedure not supported on JS target") } -get_page_size :: proc "contextless" () -> int { +@(require_results) +get_page_size :: proc() -> int { unimplemented("core:os procedure not supported on JS target") } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { unimplemented("core:os procedure not supported on JS target") } @@ -246,6 +289,7 @@ exit :: proc "contextless" (code: int) -> ! { +@(require_results) current_thread_id :: proc "contextless" () -> int { context = runtime.default_context() unimplemented("core:os procedure not supported on JS target") @@ -253,6 +297,7 @@ current_thread_id :: proc "contextless" () -> int { +@(require_results) _alloc_command_line_arguments :: proc() -> []string { return nil } diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 65eea6760..1e110d18f 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -20,148 +20,148 @@ import "base:intrinsics" // all that about compatibility. But we don't want to push experimental changes // and have people's code break while it's still work in progress. import unix "core:sys/unix" +import linux "core:sys/linux" Handle :: distinct i32 Pid :: distinct i32 File_Time :: distinct u64 -Errno :: distinct i32 Socket :: distinct int INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno : 0 -EPERM: Errno : 1 -ENOENT: Errno : 2 -ESRCH: Errno : 3 -EINTR: Errno : 4 -EIO: Errno : 5 -ENXIO: Errno : 6 -EBADF: Errno : 9 -EAGAIN: Errno : 11 -ENOMEM: Errno : 12 -EACCES: Errno : 13 -EFAULT: Errno : 14 -EEXIST: Errno : 17 -ENODEV: Errno : 19 -ENOTDIR: Errno : 20 -EISDIR: Errno : 21 -EINVAL: Errno : 22 -ENFILE: Errno : 23 -EMFILE: Errno : 24 -ETXTBSY: Errno : 26 -EFBIG: Errno : 27 -ENOSPC: Errno : 28 -ESPIPE: Errno : 29 -EROFS: Errno : 30 -EPIPE: Errno : 32 +_Platform_Error :: linux.Errno +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +EBADF :: Platform_Error.EBADF +EAGAIN :: Platform_Error.EAGAIN +ENOMEM :: Platform_Error.ENOMEM +EACCES :: Platform_Error.EACCES +EFAULT :: Platform_Error.EFAULT +EEXIST :: Platform_Error.EEXIST +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EPIPE :: Platform_Error.EPIPE -ERANGE: Errno : 34 /* Result too large */ -EDEADLK: Errno : 35 /* Resource deadlock would occur */ -ENAMETOOLONG: Errno : 36 /* File name too long */ -ENOLCK: Errno : 37 /* No record locks available */ +ERANGE :: Platform_Error.ERANGE /* Result too large */ +EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock would occur */ +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ +ENOLCK :: Platform_Error.ENOLCK /* No record locks available */ -ENOSYS: Errno : 38 /* Invalid system call number */ +ENOSYS :: Platform_Error.ENOSYS /* Invalid system call number */ -ENOTEMPTY: Errno : 39 /* Directory not empty */ -ELOOP: Errno : 40 /* Too many symbolic links encountered */ -EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ -ENOMSG: Errno : 42 /* No message of desired type */ -EIDRM: Errno : 43 /* Identifier removed */ -ECHRNG: Errno : 44 /* Channel number out of range */ -EL2NSYNC: Errno : 45 /* Level 2 not synchronized */ -EL3HLT: Errno : 46 /* Level 3 halted */ -EL3RST: Errno : 47 /* Level 3 reset */ -ELNRNG: Errno : 48 /* Link number out of range */ -EUNATCH: Errno : 49 /* Protocol driver not attached */ -ENOCSI: Errno : 50 /* No CSI structure available */ -EL2HLT: Errno : 51 /* Level 2 halted */ -EBADE: Errno : 52 /* Invalid exchange */ -EBADR: Errno : 53 /* Invalid request descriptor */ -EXFULL: Errno : 54 /* Exchange full */ -ENOANO: Errno : 55 /* No anode */ -EBADRQC: Errno : 56 /* Invalid request code */ -EBADSLT: Errno : 57 /* Invalid slot */ -EDEADLOCK: Errno : EDEADLK -EBFONT: Errno : 59 /* Bad font file format */ -ENOSTR: Errno : 60 /* Device not a stream */ -ENODATA: Errno : 61 /* No data available */ -ETIME: Errno : 62 /* Timer expired */ -ENOSR: Errno : 63 /* Out of streams resources */ -ENONET: Errno : 64 /* Machine is not on the network */ -ENOPKG: Errno : 65 /* Package not installed */ -EREMOTE: Errno : 66 /* Object is remote */ -ENOLINK: Errno : 67 /* Link has been severed */ -EADV: Errno : 68 /* Advertise error */ -ESRMNT: Errno : 69 /* Srmount error */ -ECOMM: Errno : 70 /* Communication error on send */ -EPROTO: Errno : 71 /* Protocol error */ -EMULTIHOP: Errno : 72 /* Multihop attempted */ -EDOTDOT: Errno : 73 /* RFS specific error */ -EBADMSG: Errno : 74 /* Not a data message */ -EOVERFLOW: Errno : 75 /* Value too large for defined data type */ -ENOTUNIQ: Errno : 76 /* Name not unique on network */ -EBADFD: Errno : 77 /* File descriptor in bad state */ -EREMCHG: Errno : 78 /* Remote address changed */ -ELIBACC: Errno : 79 /* Can not access a needed shared library */ -ELIBBAD: Errno : 80 /* Accessing a corrupted shared library */ -ELIBSCN: Errno : 81 /* .lib section in a.out corrupted */ -ELIBMAX: Errno : 82 /* Attempting to link in too many shared libraries */ -ELIBEXEC: Errno : 83 /* Cannot exec a shared library directly */ -EILSEQ: Errno : 84 /* Illegal byte sequence */ -ERESTART: Errno : 85 /* Interrupted system call should be restarted */ -ESTRPIPE: Errno : 86 /* Streams pipe error */ -EUSERS: Errno : 87 /* Too many users */ -ENOTSOCK: Errno : 88 /* Socket operation on non-socket */ -EDESTADDRREQ: Errno : 89 /* Destination address required */ -EMSGSIZE: Errno : 90 /* Message too long */ -EPROTOTYPE: Errno : 91 /* Protocol wrong type for socket */ -ENOPROTOOPT: Errno : 92 /* Protocol not available */ -EPROTONOSUPPORT:Errno : 93 /* Protocol not supported */ -ESOCKTNOSUPPORT:Errno : 94 /* Socket type not supported */ -EOPNOTSUPP: Errno : 95 /* Operation not supported on transport endpoint */ -EPFNOSUPPORT: Errno : 96 /* Protocol family not supported */ -EAFNOSUPPORT: Errno : 97 /* Address family not supported by protocol */ -EADDRINUSE: Errno : 98 /* Address already in use */ -EADDRNOTAVAIL: Errno : 99 /* Cannot assign requested address */ -ENETDOWN: Errno : 100 /* Network is down */ -ENETUNREACH: Errno : 101 /* Network is unreachable */ -ENETRESET: Errno : 102 /* Network dropped connection because of reset */ -ECONNABORTED: Errno : 103 /* Software caused connection abort */ -ECONNRESET: Errno : 104 /* Connection reset by peer */ -ENOBUFS: Errno : 105 /* No buffer space available */ -EISCONN: Errno : 106 /* Transport endpoint is already connected */ -ENOTCONN: Errno : 107 /* Transport endpoint is not connected */ -ESHUTDOWN: Errno : 108 /* Cannot send after transport endpoint shutdown */ -ETOOMANYREFS: Errno : 109 /* Too many references: cannot splice */ -ETIMEDOUT: Errno : 110 /* Connection timed out */ -ECONNREFUSED: Errno : 111 /* Connection refused */ -EHOSTDOWN: Errno : 112 /* Host is down */ -EHOSTUNREACH: Errno : 113 /* No route to host */ -EALREADY: Errno : 114 /* Operation already in progress */ -EINPROGRESS: Errno : 115 /* Operation now in progress */ -ESTALE: Errno : 116 /* Stale file handle */ -EUCLEAN: Errno : 117 /* Structure needs cleaning */ -ENOTNAM: Errno : 118 /* Not a XENIX named type file */ -ENAVAIL: Errno : 119 /* No XENIX semaphores available */ -EISNAM: Errno : 120 /* Is a named type file */ -EREMOTEIO: Errno : 121 /* Remote I/O error */ -EDQUOT: Errno : 122 /* Quota exceeded */ +ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ +ELOOP :: Platform_Error.ELOOP /* Too many symbolic links encountered */ +EWOULDBLOCK :: Platform_Error.EWOULDBLOCK /* Operation would block */ +ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ +EIDRM :: Platform_Error.EIDRM /* Identifier removed */ +ECHRNG :: Platform_Error.ECHRNG /* Channel number out of range */ +EL2NSYNC :: Platform_Error.EL2NSYNC /* Level 2 not synchronized */ +EL3HLT :: Platform_Error.EL3HLT /* Level 3 halted */ +EL3RST :: Platform_Error.EL3RST /* Level 3 reset */ +ELNRNG :: Platform_Error.ELNRNG /* Link number out of range */ +EUNATCH :: Platform_Error.EUNATCH /* Protocol driver not attached */ +ENOCSI :: Platform_Error.ENOCSI /* No CSI structure available */ +EL2HLT :: Platform_Error.EL2HLT /* Level 2 halted */ +EBADE :: Platform_Error.EBADE /* Invalid exchange */ +EBADR :: Platform_Error.EBADR /* Invalid request descriptor */ +EXFULL :: Platform_Error.EXFULL /* Exchange full */ +ENOANO :: Platform_Error.ENOANO /* No anode */ +EBADRQC :: Platform_Error.EBADRQC /* Invalid request code */ +EBADSLT :: Platform_Error.EBADSLT /* Invalid slot */ +EDEADLOCK :: Platform_Error.EDEADLOCK +EBFONT :: Platform_Error.EBFONT /* Bad font file format */ +ENOSTR :: Platform_Error.ENOSTR /* Device not a stream */ +ENODATA :: Platform_Error.ENODATA /* No data available */ +ETIME :: Platform_Error.ETIME /* Timer expired */ +ENOSR :: Platform_Error.ENOSR /* Out of streams resources */ +ENONET :: Platform_Error.ENONET /* Machine is not on the network */ +ENOPKG :: Platform_Error.ENOPKG /* Package not installed */ +EREMOTE :: Platform_Error.EREMOTE /* Object is remote */ +ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ +EADV :: Platform_Error.EADV /* Advertise error */ +ESRMNT :: Platform_Error.ESRMNT /* Srmount error */ +ECOMM :: Platform_Error.ECOMM /* Communication error on send */ +EPROTO :: Platform_Error.EPROTO /* Protocol error */ +EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ +EDOTDOT :: Platform_Error.EDOTDOT /* RFS specific error */ +EBADMSG :: Platform_Error.EBADMSG /* Not a data message */ +EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large for defined data type */ +ENOTUNIQ :: Platform_Error.ENOTUNIQ /* Name not unique on network */ +EBADFD :: Platform_Error.EBADFD /* File descriptor in bad state */ +EREMCHG :: Platform_Error.EREMCHG /* Remote address changed */ +ELIBACC :: Platform_Error.ELIBACC /* Can not access a needed shared library */ +ELIBBAD :: Platform_Error.ELIBBAD /* Accessing a corrupted shared library */ +ELIBSCN :: Platform_Error.ELIBSCN /* .lib section in a.out corrupted */ +ELIBMAX :: Platform_Error.ELIBMAX /* Attempting to link in too many shared libraries */ +ELIBEXEC :: Platform_Error.ELIBEXEC /* Cannot exec a shared library directly */ +EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ +ERESTART :: Platform_Error.ERESTART /* Interrupted system call should be restarted */ +ESTRPIPE :: Platform_Error.ESTRPIPE /* Streams pipe error */ +EUSERS :: Platform_Error.EUSERS /* Too many users */ +ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ +EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ +EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol not available */ +EPROTONOSUPPOR :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ +ESOCKTNOSUPPOR :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported on transport endpoint */ +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol */ +EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Cannot assign requested address */ +ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ +ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ +ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection because of reset */ +ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ +ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ +ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ +EISCONN :: Platform_Error.EISCONN /* Transport endpoint is already connected */ +ENOTCONN :: Platform_Error.ENOTCONN /* Transport endpoint is not connected */ +ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Cannot send after transport endpoint shutdown */ +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: cannot splice */ +ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Connection timed out */ +ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ +EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ +EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ +EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ +ESTALE :: Platform_Error.ESTALE /* Stale file handle */ +EUCLEAN :: Platform_Error.EUCLEAN /* Structure needs cleaning */ +ENOTNAM :: Platform_Error.ENOTNAM /* Not a XENIX named type file */ +ENAVAIL :: Platform_Error.ENAVAIL /* No XENIX semaphores available */ +EISNAM :: Platform_Error.EISNAM /* Is a named type file */ +EREMOTEIO :: Platform_Error.EREMOTEIO /* Remote I/O error */ +EDQUOT :: Platform_Error.EDQUOT /* Quota exceeded */ -ENOMEDIUM: Errno : 123 /* No medium found */ -EMEDIUMTYPE: Errno : 124 /* Wrong medium type */ -ECANCELED: Errno : 125 /* Operation Canceled */ -ENOKEY: Errno : 126 /* Required key not available */ -EKEYEXPIRED: Errno : 127 /* Key has expired */ -EKEYREVOKED: Errno : 128 /* Key has been revoked */ -EKEYREJECTED: Errno : 129 /* Key was rejected by service */ +ENOMEDIUM :: Platform_Error.ENOMEDIUM /* No medium found */ +EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE /* Wrong medium type */ +ECANCELED :: Platform_Error.ECANCELED /* Operation Canceled */ +ENOKEY :: Platform_Error.ENOKEY /* Required key not available */ +EKEYEXPIRED :: Platform_Error.EKEYEXPIRED /* Key has expired */ +EKEYREVOKED :: Platform_Error.EKEYREVOKED /* Key has been revoked */ +EKEYREJECTED :: Platform_Error.EKEYREJECTED /* Key was rejected by service */ /* for robust mutexes */ -EOWNERDEAD: Errno : 130 /* Owner died */ -ENOTRECOVERABLE: Errno : 131 /* State not recoverable */ +EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Owner died */ +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ -ERFKILL: Errno : 132 /* Operation not possible due to RF-kill */ +ERFKILL :: Platform_Error.ERFKILL /* Operation not possible due to RF-kill */ -EHWPOISON: Errno : 133 /* Memory page has hardware error */ +EHWPOISON :: Platform_Error.EHWPOISON /* Memory page has hardware error */ ADDR_NO_RANDOMIZE :: 0x40000 @@ -448,13 +448,13 @@ S_ISGID :: 0o2000 // Set group id on execution S_ISVTX :: 0o1000 // Directory restrcted delete -S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } F_OK :: 0 // Test for file existance X_OK :: 1 // Test for execute permission @@ -506,41 +506,55 @@ foreign dl { @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- } +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } // determine errno from syscall return value -@private -_get_errno :: proc(res: int) -> Errno { +@(private, require_results) +_get_errno :: proc(res: int) -> Error { if res < 0 && res > -4096 { - return Errno(-res) + return Platform_Error(-res) } - return 0 + return nil } // get errno from libc -get_last_error :: proc "contextless" () -> int { - return int(__errno_location()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + err := Platform_Error(__errno_location()^) + #partial switch err { + case .NONE: + return nil + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + } + return err } -personality :: proc(persona: u64) -> (Errno) { +personality :: proc(persona: u64) -> Error { res := unix.sys_personality(persona) if res == -1 { return _get_errno(res) } - return ERROR_NONE + return nil } -fork :: proc() -> (Pid, Errno) { +@(require_results) +fork :: proc() -> (Pid, Error) { pid := unix.sys_fork() if pid == -1 { return -1, _get_errno(pid) } - return Pid(pid), ERROR_NONE + return Pid(pid), nil } -execvp :: proc(path: string, args: []string) -> Errno { +execvp :: proc(path: string, args: []string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -551,24 +565,30 @@ execvp :: proc(path: string, args: []string) -> Errno { } _unix_execvp(path_cstr, raw_data(args_cstrs)) - return Errno(get_last_error()) + return get_last_error() } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) handle := unix.sys_open(cstr, flags, uint(mode)) if handle < 0 { return INVALID_HANDLE, _get_errno(handle) } - return Handle(handle), ERROR_NONE + return Handle(handle), nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { return _get_errno(unix.sys_close(int(fd))) } +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil +} + // If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error). // `SSIZE_MAX` is also implementation defined but usually the max of a `ssize_t` which is `max(int)` in Odin. // In practice a read/write call would probably never read/write these big buffers all at once, @@ -578,9 +598,9 @@ close :: proc(fd: Handle) -> Errno { @(private) MAX_RW :: 1 << 30 -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { +read :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_read := min(uint(len(data)), MAX_RW) @@ -589,12 +609,12 @@ read :: proc(fd: Handle, data: []byte) -> (int, Errno) { if bytes_read < 0 { return -1, _get_errno(bytes_read) } - return bytes_read, ERROR_NONE + return bytes_read, nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(uint(len(data)), MAX_RW) @@ -603,12 +623,12 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) { if bytes_written < 0 { return -1, _get_errno(bytes_written) } - return bytes_written, ERROR_NONE + return bytes_written, nil } -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_read := min(uint(len(data)), MAX_RW) @@ -617,12 +637,12 @@ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { if bytes_read < 0 { return -1, _get_errno(bytes_read) } - return bytes_read, ERROR_NONE + return bytes_read, nil } -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(uint(len(data)), MAX_RW) @@ -631,92 +651,97 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { if bytes_written < 0 { return -1, _get_errno(bytes_written) } - return bytes_written, ERROR_NONE + return bytes_written, nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { res := unix.sys_lseek(int(fd), offset, whence) if res < 0 { return -1, _get_errno(int(res)) } - return i64(res), ERROR_NONE + return i64(res), nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { // deliberately uninitialized; the syscall fills this buffer for us s: OS_Stat = --- result := unix.sys_fstat(int(fd), rawptr(&s)) if result < 0 { return 0, _get_errno(result) } - return max(s.size, 0), ERROR_NONE + return max(s.size, 0), nil } -rename :: proc(old_path, new_path: string) -> Errno { +rename :: proc(old_path, new_path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr)) } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _get_errno(unix.sys_unlink(path_cstr)) } -make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno { +make_directory :: proc(path: string, mode: u32 = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _get_errno(unix.sys_mkdir(path_cstr, uint(mode))) } -remove_directory :: proc(path: string) -> Errno { +remove_directory :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _get_errno(unix.sys_rmdir(path_cstr)) } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -725,6 +750,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +@(require_results) exists :: proc(path: string) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cpath := strings.clone_to_cstring(path, context.temp_allocator) @@ -742,26 +768,22 @@ stderr: Handle = 2 last_write_time :: proc(fd: Handle) -> File_Time {} last_write_time_by_name :: proc(name: string) -> File_Time {} */ -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { - s, err := _stat(name) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -771,11 +793,11 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) { if result < 0 { return s, _get_errno(result) } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -785,53 +807,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { if result < 0 { return s, _get_errno(result) } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized; the syscall fills this buffer for us s: OS_Stat = --- result := unix.sys_fstat(int(fd), rawptr(&s)) if result < 0 { return s, _get_errno(result) } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE + err = nil if result == nil { end_of_stream = true @@ -842,8 +864,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -860,12 +882,13 @@ _readlink :: proc(path: string) -> (string, Errno) { delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } } -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { buf : [256]byte fd_str := strconv.itoa( buf[:], cast(int)fd ) @@ -875,7 +898,8 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { return _readlink(procfs_path) } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -886,25 +910,26 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } defer _unix_free(path_ptr) path = strings.clone(string(cstring(path_ptr))) - return path, ERROR_NONE + return path, nil } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) result := unix.sys_access(cstr, mask) if result < 0 { return false, _get_errno(result) } - return true, ERROR_NONE + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) @@ -916,33 +941,35 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } -set_env :: proc(key, value: string) -> Errno { +set_env :: proc(key, value: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() key_cstring := strings.clone_to_cstring(key, context.temp_allocator) value_cstring := strings.clone_to_cstring(value, context.temp_allocator) // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly res := _unix_setenv(key_cstring, value_cstring, 1) if res < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -unset_env :: proc(key: string) -> Errno { +unset_env :: proc(key: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() s := strings.clone_to_cstring(key, context.temp_allocator) res := _unix_putenv(s) if res < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } +@(require_results) get_current_directory :: proc() -> string { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. @@ -964,14 +991,14 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := unix.sys_chdir(cstr) if res < 0 { return _get_errno(res) } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -979,16 +1006,19 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(c.int(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return unix.sys_gettid() } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) handle := _unix_dlopen(cstr, c.int(flags)) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -1004,7 +1034,8 @@ dlerror :: proc() -> string { return string(_unix_dlerror()) } -get_page_size :: proc "contextless" () -> int { +@(require_results) +get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @static page_size := -1 @@ -1016,11 +1047,12 @@ get_page_size :: proc "contextless" () -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { return int(_unix_get_nprocs()) } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { @@ -1029,117 +1061,120 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) { +@(require_results) +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { result := unix.sys_socket(domain, type, protocol) if result < 0 { return 0, _get_errno(result) } - return Socket(result), ERROR_NONE + return Socket(result), nil } -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { result := unix.sys_bind(int(sd), addr, len) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { result := unix.sys_connect(int(sd), addr, len) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) { +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { result := unix.sys_accept(int(sd), rawptr(addr), len) if result < 0 { return 0, _get_errno(result) } - return Socket(result), ERROR_NONE + return Socket(result), nil } -listen :: proc(sd: Socket, backlog: int) -> (Errno) { +listen :: proc(sd: Socket, backlog: int) -> Error { result := unix.sys_listen(int(sd), backlog) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) { +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) { +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size)) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) { +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -shutdown :: proc(sd: Socket, how: int) -> (Errno) { +shutdown :: proc(sd: Socket, how: int) -> Error { result := unix.sys_shutdown(int(sd), how) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { result := unix.sys_fcntl(fd, cmd, arg) if result < 0 { return 0, _get_errno(result) } - return result, ERROR_NONE + return result, nil } -poll :: proc(fds: []pollfd, timeout: int) -> (int, Errno) { +@(require_results) +poll :: proc(fds: []pollfd, timeout: int) -> (int, Error) { result := unix.sys_poll(raw_data(fds), uint(len(fds)), timeout) if result < 0 { return 0, _get_errno(result) } - return result, ERROR_NONE + return result, nil } -ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) { +@(require_results) +ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Error) { result := unix.sys_ppoll(raw_data(fds), uint(len(fds)), timeout, sigmask, size_of(sigset_t)) if result < 0 { return 0, _get_errno(result) } - return result, ERROR_NONE + return result, nil } diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index 2bda8abf7..c41dc6aa6 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -9,150 +9,289 @@ import "core:c" Handle :: distinct i32 File_Time :: distinct u64 -Errno :: distinct i32 INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno : 0 /* No error */ -EPERM: Errno : 1 /* Operation not permitted */ -ENOENT: Errno : 2 /* No such file or directory */ -EINTR: Errno : 4 /* Interrupted system call */ -ESRCH: Errno : 3 /* No such process */ -EIO: Errno : 5 /* Input/output error */ -ENXIO: Errno : 6 /* Device not configured */ -E2BIG: Errno : 7 /* Argument list too long */ -ENOEXEC: Errno : 8 /* Exec format error */ -EBADF: Errno : 9 /* Bad file descriptor */ -ECHILD: Errno : 10 /* No child processes */ -EDEADLK: Errno : 11 /* Resource deadlock avoided. 11 was EAGAIN */ -ENOMEM: Errno : 12 /* Cannot allocate memory */ -EACCES: Errno : 13 /* Permission denied */ -EFAULT: Errno : 14 /* Bad address */ -ENOTBLK: Errno : 15 /* Block device required */ -EBUSY: Errno : 16 /* Device busy */ -EEXIST: Errno : 17 /* File exists */ -EXDEV: Errno : 18 /* Cross-device link */ -ENODEV: Errno : 19 /* Operation not supported by device */ -ENOTDIR: Errno : 20 /* Not a directory */ -EISDIR: Errno : 21 /* Is a directory */ -EINVAL: Errno : 22 /* Invalid argument */ -ENFILE: Errno : 23 /* Too many open files in system */ -EMFILE: Errno : 24 /* Too many open files */ -ENOTTY: Errno : 25 /* Inappropriate ioctl for device */ -ETXTBSY: Errno : 26 /* Text file busy */ -EFBIG: Errno : 27 /* File too large */ -ENOSPC: Errno : 28 /* No space left on device */ -ESPIPE: Errno : 29 /* Illegal seek */ -EROFS: Errno : 30 /* Read-only file system */ -EMLINK: Errno : 31 /* Too many links */ -EPIPE: Errno : 32 /* Broken pipe */ +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + EINTR = 4, /* Interrupted system call */ + ESRCH = 3, /* No such process */ + EIO = 5, /* Input/output error */ + ENXIO = 6, /* Device not configured */ + E2BIG = 7, /* Argument list too long */ + ENOEXEC = 8, /* Exec format error */ + EBADF = 9, /* Bad file descriptor */ + ECHILD = 10, /* No child processes */ + EDEADLK = 11, /* Resource deadlock avoided. 11 was EAGAIN */ + ENOMEM = 12, /* Cannot allocate memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* Operation not supported by device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* Too many open files in system */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Inappropriate ioctl for device */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + + /* math software */ + EDOM = 33, /* Numerical argument out of domain */ + ERANGE = 34, /* Result too large or too small */ + + /* non-blocking and interrupt i/o */ + EAGAIN = 35, /* Resource temporarily unavailable */ + EWOULDBLOCK = EAGAIN, /* Operation would block */ + EINPROGRESS = 36, /* Operation now in progress */ + EALREADY = 37, /* Operation already in progress */ + + /* ipc/network software -- argument errors */ + ENOTSOCK = 38, /* Socket operation on non-socket */ + EDESTADDRREQ = 39, /* Destination address required */ + EMSGSIZE = 40, /* Message too long */ + EPROTOTYPE = 41, /* Protocol wrong type for socket */ + ENOPROTOOPT = 42, /* Protocol option not available */ + EPROTONOSUPPORT = 43, /* Protocol not supported */ + ESOCKTNOSUPPORT = 44, /* Socket type not supported */ + EOPNOTSUPP = 45, /* Operation not supported */ + EPFNOSUPPORT = 46, /* Protocol family not supported */ + EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ + EADDRINUSE = 48, /* Address already in use */ + EADDRNOTAVAIL = 49, /* Can't assign requested address */ + + /* ipc/network software -- operational errors */ + ENETDOWN = 50, /* Network is down */ + ENETUNREACH = 51, /* Network is unreachable */ + ENETRESET = 52, /* Network dropped connection on reset */ + ECONNABORTED = 53, /* Software caused connection abort */ + ECONNRESET = 54, /* Connection reset by peer */ + ENOBUFS = 55, /* No buffer space available */ + EISCONN = 56, /* Socket is already connected */ + ENOTCONN = 57, /* Socket is not connected */ + ESHUTDOWN = 58, /* Can't send after socket shutdown */ + ETOOMANYREFS = 59, /* Too many references: can't splice */ + ETIMEDOUT = 60, /* Operation timed out */ + ECONNREFUSED = 61, /* Connection refused */ + + ELOOP = 62, /* Too many levels of symbolic links */ + ENAMETOOLONG = 63, /* File name too long */ + + /* should be rearranged */ + EHOSTDOWN = 64, /* Host is down */ + EHOSTUNREACH = 65, /* No route to host */ + ENOTEMPTY = 66, /* Directory not empty */ + + /* quotas & mush */ + EPROCLIM = 67, /* Too many processes */ + EUSERS = 68, /* Too many users */ + EDQUOT = 69, /* Disc quota exceeded */ + + /* Network File System */ + ESTALE = 70, /* Stale NFS file handle */ + EREMOTE = 71, /* Too many levels of remote in path */ + EBADRPC = 72, /* RPC struct is bad */ + ERPCMISMATCH = 73, /* RPC version wrong */ + EPROGUNAVAIL = 74, /* RPC prog. not avail */ + EPROGMISMATCH = 75, /* Program version wrong */ + EPROCUNAVAIL = 76, /* Bad procedure for program */ + + ENOLCK = 77, /* No locks available */ + ENOSYS = 78, /* Function not implemented */ + + EFTYPE = 79, /* Inappropriate file type or format */ + EAUTH = 80, /* Authentication error */ + ENEEDAUTH = 81, /* Need authenticator */ + + /* SystemV IPC */ + EIDRM = 82, /* Identifier removed */ + ENOMSG = 83, /* No message of desired type */ + EOVERFLOW = 84, /* Value too large to be stored in data type */ + + /* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ + EILSEQ = 85, /* Illegal byte sequence */ + + /* From IEEE Std 1003.1-2001 */ + /* Base, Realtime, Threads or Thread Priority Scheduling option errors */ + ENOTSUP = 86, /* Not supported */ + + /* Realtime option errors */ + ECANCELED = 87, /* Operation canceled */ + + /* Realtime, XSI STREAMS option errors */ + EBADMSG = 88, /* Bad or Corrupt message */ + + /* XSI STREAMS option errors */ + ENODATA = 89, /* No message available */ + ENOSR = 90, /* No STREAM resources */ + ENOSTR = 91, /* Not a STREAM */ + ETIME = 92, /* STREAM ioctl timeout */ + + /* File system extended attribute errors */ + ENOATTR = 93, /* Attribute not found */ + + /* Realtime, XSI STREAMS option errors */ + EMULTIHOP = 94, /* Multihop attempted */ + ENOLINK = 95, /* Link has been severed */ + EPROTO = 96, /* Protocol error */ + + /* Robust mutexes */ + EOWNERDEAD = 97, /* Previous owner died */ + ENOTRECOVERABLE = 98, /* State not recoverable */ + + ELAST = 98, /* Must equal largest Error */ +} + +EPERM :: Platform_Error.EPERM /* Operation not permitted */ +ENOENT :: Platform_Error.ENOENT /* No such file or directory */ +EINTR :: Platform_Error.EINTR /* Interrupted system call */ +ESRCH :: Platform_Error.ESRCH /* No such process */ +EIO :: Platform_Error.EIO /* Input/output error */ +ENXIO :: Platform_Error.ENXIO /* Device not configured */ +E2BIG :: Platform_Error.E2BIG /* Argument list too long */ +ENOEXEC :: Platform_Error.ENOEXEC /* Exec format error */ +EBADF :: Platform_Error.EBADF /* Bad file descriptor */ +ECHILD :: Platform_Error.ECHILD /* No child processes */ +EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock avoided. 11 was EAGAIN */ +ENOMEM :: Platform_Error.ENOMEM /* Cannot allocate memory */ +EACCES :: Platform_Error.EACCES /* Permission denied */ +EFAULT :: Platform_Error.EFAULT /* Bad address */ +ENOTBLK :: Platform_Error.ENOTBLK /* Block device required */ +EBUSY :: Platform_Error.EBUSY /* Device busy */ +EEXIST :: Platform_Error.EEXIST /* File exists */ +EXDEV :: Platform_Error.EXDEV /* Cross-device link */ +ENODEV :: Platform_Error.ENODEV /* Operation not supported by device */ +ENOTDIR :: Platform_Error.ENOTDIR /* Not a directory */ +EISDIR :: Platform_Error.EISDIR /* Is a directory */ +EINVAL :: Platform_Error.EINVAL /* Invalid argument */ +ENFILE :: Platform_Error.ENFILE /* Too many open files in system */ +EMFILE :: Platform_Error.EMFILE /* Too many open files */ +ENOTTY :: Platform_Error.ENOTTY /* Inappropriate ioctl for device */ +ETXTBSY :: Platform_Error.ETXTBSY /* Text file busy */ +EFBIG :: Platform_Error.EFBIG /* File too large */ +ENOSPC :: Platform_Error.ENOSPC /* No space left on device */ +ESPIPE :: Platform_Error.ESPIPE /* Illegal seek */ +EROFS :: Platform_Error.EROFS /* Read-only file system */ +EMLINK :: Platform_Error.EMLINK /* Too many links */ +EPIPE :: Platform_Error.EPIPE /* Broken pipe */ /* math software */ -EDOM: Errno : 33 /* Numerical argument out of domain */ -ERANGE: Errno : 34 /* Result too large or too small */ +EDOM :: Platform_Error.EDOM /* Numerical argument out of domain */ +ERANGE :: Platform_Error.ERANGE /* Result too large or too small */ /* non-blocking and interrupt i/o */ -EAGAIN: Errno : 35 /* Resource temporarily unavailable */ -EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ -EINPROGRESS: Errno : 36 /* Operation now in progress */ -EALREADY: Errno : 37 /* Operation already in progress */ +EAGAIN :: Platform_Error.EAGAIN /* Resource temporarily unavailable */ +EWOULDBLOCK :: EAGAIN /* Operation would block */ +EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ +EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ /* ipc/network software -- argument errors */ -ENOTSOCK: Errno : 38 /* Socket operation on non-socket */ -EDESTADDRREQ: Errno : 39 /* Destination address required */ -EMSGSIZE: Errno : 40 /* Message too long */ -EPROTOTYPE: Errno : 41 /* Protocol wrong type for socket */ -ENOPROTOOPT: Errno : 42 /* Protocol option not available */ -EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ -ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ -EOPNOTSUPP: Errno : 45 /* Operation not supported */ -EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ -EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ -EADDRINUSE: Errno : 48 /* Address already in use */ -EADDRNOTAVAIL: Errno : 49 /* Can't assign requested address */ +ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ +EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ +EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol option not available */ +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported */ +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol family */ +EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Can't assign requested address */ /* ipc/network software -- operational errors */ -ENETDOWN: Errno : 50 /* Network is down */ -ENETUNREACH: Errno : 51 /* Network is unreachable */ -ENETRESET: Errno : 52 /* Network dropped connection on reset */ -ECONNABORTED: Errno : 53 /* Software caused connection abort */ -ECONNRESET: Errno : 54 /* Connection reset by peer */ -ENOBUFS: Errno : 55 /* No buffer space available */ -EISCONN: Errno : 56 /* Socket is already connected */ -ENOTCONN: Errno : 57 /* Socket is not connected */ -ESHUTDOWN: Errno : 58 /* Can't send after socket shutdown */ -ETOOMANYREFS: Errno : 59 /* Too many references: can't splice */ -ETIMEDOUT: Errno : 60 /* Operation timed out */ -ECONNREFUSED: Errno : 61 /* Connection refused */ +ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ +ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ +ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection on reset */ +ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ +ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ +ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ +EISCONN :: Platform_Error.EISCONN /* Socket is already connected */ +ENOTCONN :: Platform_Error.ENOTCONN /* Socket is not connected */ +ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Can't send after socket shutdown */ +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: can't splice */ +ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Operation timed out */ +ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ -ELOOP: Errno : 62 /* Too many levels of symbolic links */ -ENAMETOOLONG: Errno : 63 /* File name too long */ +ELOOP :: Platform_Error.ELOOP /* Too many levels of symbolic links */ +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ /* should be rearranged */ -EHOSTDOWN: Errno : 64 /* Host is down */ -EHOSTUNREACH: Errno : 65 /* No route to host */ -ENOTEMPTY: Errno : 66 /* Directory not empty */ +EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ +ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ /* quotas & mush */ -EPROCLIM: Errno : 67 /* Too many processes */ -EUSERS: Errno : 68 /* Too many users */ -EDQUOT: Errno : 69 /* Disc quota exceeded */ +EPROCLIM :: Platform_Error.EPROCLIM /* Too many processes */ +EUSERS :: Platform_Error.EUSERS /* Too many users */ +EDQUOT :: Platform_Error.EDQUOT /* Disc quota exceeded */ /* Network File System */ -ESTALE: Errno : 70 /* Stale NFS file handle */ -EREMOTE: Errno : 71 /* Too many levels of remote in path */ -EBADRPC: Errno : 72 /* RPC struct is bad */ -ERPCMISMATCH: Errno : 73 /* RPC version wrong */ -EPROGUNAVAIL: Errno : 74 /* RPC prog. not avail */ -EPROGMISMATCH: Errno : 75 /* Program version wrong */ -EPROCUNAVAIL: Errno : 76 /* Bad procedure for program */ +ESTALE :: Platform_Error.ESTALE /* Stale NFS file handle */ +EREMOTE :: Platform_Error.EREMOTE /* Too many levels of remote in path */ +EBADRPC :: Platform_Error.EBADRPC /* RPC struct is bad */ +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH /* RPC version wrong */ +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL /* RPC prog. not avail */ +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH /* Program version wrong */ +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL /* Bad procedure for program */ -ENOLCK: Errno : 77 /* No locks available */ -ENOSYS: Errno : 78 /* Function not implemented */ +ENOLCK :: Platform_Error.ENOLCK /* No locks available */ +ENOSYS :: Platform_Error.ENOSYS /* Function not implemented */ -EFTYPE: Errno : 79 /* Inappropriate file type or format */ -EAUTH: Errno : 80 /* Authentication error */ -ENEEDAUTH: Errno : 81 /* Need authenticator */ +EFTYPE :: Platform_Error.EFTYPE /* Inappropriate file type or format */ +EAUTH :: Platform_Error.EAUTH /* Authentication error */ +ENEEDAUTH :: Platform_Error.ENEEDAUTH /* Need authenticator */ /* SystemV IPC */ -EIDRM: Errno : 82 /* Identifier removed */ -ENOMSG: Errno : 83 /* No message of desired type */ -EOVERFLOW: Errno : 84 /* Value too large to be stored in data type */ +EIDRM :: Platform_Error.EIDRM /* Identifier removed */ +ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ +EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large to be stored in data type */ /* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ -EILSEQ: Errno : 85 /* Illegal byte sequence */ +EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ /* From IEEE Std 1003.1-2001 */ /* Base, Realtime, Threads or Thread Priority Scheduling option errors */ -ENOTSUP: Errno : 86 /* Not supported */ +ENOTSUP :: Platform_Error.ENOTSUP /* Not supported */ /* Realtime option errors */ -ECANCELED: Errno : 87 /* Operation canceled */ +ECANCELED :: Platform_Error.ECANCELED /* Operation canceled */ /* Realtime, XSI STREAMS option errors */ -EBADMSG: Errno : 88 /* Bad or Corrupt message */ +EBADMSG :: Platform_Error.EBADMSG /* Bad or Corrupt message */ /* XSI STREAMS option errors */ -ENODATA: Errno : 89 /* No message available */ -ENOSR: Errno : 90 /* No STREAM resources */ -ENOSTR: Errno : 91 /* Not a STREAM */ -ETIME: Errno : 92 /* STREAM ioctl timeout */ +ENODATA :: Platform_Error.ENODATA /* No message available */ +ENOSR :: Platform_Error.ENOSR /* No STREAM resources */ +ENOSTR :: Platform_Error.ENOSTR /* Not a STREAM */ +ETIME :: Platform_Error.ETIME /* STREAM ioctl timeout */ /* File system extended attribute errors */ -ENOATTR: Errno : 93 /* Attribute not found */ +ENOATTR :: Platform_Error.ENOATTR /* Attribute not found */ /* Realtime, XSI STREAMS option errors */ -EMULTIHOP: Errno : 94 /* Multihop attempted */ -ENOLINK: Errno : 95 /* Link has been severed */ -EPROTO: Errno : 96 /* Protocol error */ +EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ +ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ +EPROTO :: Platform_Error.EPROTO /* Protocol error */ /* Robust mutexes */ -EOWNERDEAD: Errno : 97 /* Previous owner died */ -ENOTRECOVERABLE: Errno : 98 /* State not recoverable */ +EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Previous owner died */ +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ -ELAST: Errno : 98 /* Must equal largest errno */ +ELAST :: Platform_Error.ELAST /* Must equal largest Error */ -/* end of errno */ +/* end of Error */ O_RDONLY :: 0x000000000 O_WRONLY :: 0x000000001 @@ -268,13 +407,13 @@ S_ISUID :: 0o4000 // Set user id on execution S_ISGID :: 0o2000 // Set group id on execution S_ISVTX :: 0o1000 // Directory restrcted delete -S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } F_OK :: 0 // Test for file existance X_OK :: 1 // Test for execute permission @@ -306,7 +445,7 @@ foreign libc { @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + @(link_name="__readdir_r30") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- @@ -334,154 +473,186 @@ foreign libc { // NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } -get_last_error :: proc "contextless" () -> int { - return int(__errno_location()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__errno_location()^) } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) handle := _unix_open(cstr, c.int(flags), c.int(mode)) if handle == -1 { - return INVALID_HANDLE, Errno(get_last_error()) + return INVALID_HANDLE, get_last_error() } - return handle, ERROR_NONE + return handle, nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { result := _unix_close(fd) if result == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil +} + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil } // We set a max of 1GB to keep alignment and to be safe. @(private) MAX_RW :: 1 << 30 -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { +read :: proc(fd: Handle, data: []byte) -> (int, Error) { to_read := min(c.size_t(len(data)), MAX_RW) bytes_read := _unix_read(fd, &data[0], to_read) if bytes_read == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_read), ERROR_NONE + return int(bytes_read), nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(c.size_t(len(data)), MAX_RW) bytes_written := _unix_write(fd, &data[0], to_write) if bytes_written == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_written), ERROR_NONE + return int(bytes_written), nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = read(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = write(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return res, ERROR_NONE + return res, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return -1, err - } - return s.size, ERROR_NONE +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return } -rename :: proc(old_path, new_path: string) -> Errno { +rename :: proc(old_path, new_path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) res := _unix_rename(old_path_cstr, new_path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_unlink(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_mkdir(path_cstr, mode) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove_directory :: proc(path: string) -> Errno { +remove_directory :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_rmdir(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -490,6 +661,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +@(require_results) exists :: proc(path: string) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cpath := strings.clone_to_cstring(path, context.temp_allocator) @@ -497,12 +669,13 @@ exists :: proc(path: string) -> bool { return res == 0 } -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { +@(require_results) +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg)) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return int(result), ERROR_NONE + return int(result), nil } // NOTE(bill): Uses startup to initialize it @@ -511,38 +684,34 @@ stdin: Handle = 0 stdout: Handle = 1 stderr: Handle = 2 -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { - s, err := _stat(name) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) s: OS_Stat = --- result := _unix_lstat(cstr, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -550,54 +719,54 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_lstat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { s: OS_Stat = --- result := _unix_fstat(fd, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE + err = nil if result == nil { end_of_stream = true @@ -607,8 +776,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -619,31 +788,28 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { bufsz += MAX_PATH delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } - return "", Errno{} + return "", Error{} } -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { buf: [MAX_PATH]byte - _, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) - if err != ERROR_NONE { - return "", err - } - - path := strings.clone_from_cstring(cstring(&buf[0])) - return path, err + _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return + return strings.clone_from_cstring(cstring(&buf[0])) } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -654,26 +820,27 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } defer _unix_free(path_ptr) path = strings.clone(string(cstring(path_ptr))) - return path, ERROR_NONE + return path, nil } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) result := _unix_access(cstr, c.int(mask)) if result == -1 { - return false, Errno(get_last_error()) + return false, get_last_error() } - return true, ERROR_NONE + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) @@ -685,11 +852,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) get_current_directory :: proc() -> string { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. @@ -701,7 +870,7 @@ get_current_directory :: proc() -> string { if cwd != nil { return string(cwd) } - if Errno(get_last_error()) != ERANGE { + if get_last_error() != ERANGE { delete(buf) return "" } @@ -710,14 +879,14 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -725,10 +894,12 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(c.int(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return int(_lwp_self()) } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) @@ -736,6 +907,7 @@ dlopen :: proc(filename: string, flags: int) -> rawptr { return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -749,10 +921,12 @@ dlclose :: proc(handle: rawptr) -> bool { return _unix_dlclose(handle) == 0 } +@(require_results) dlerror :: proc() -> string { return string(_unix_dlerror()) } +@(require_results) get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @@ -765,7 +939,7 @@ get_page_size :: proc() -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) @@ -778,6 +952,7 @@ _processor_core_count :: proc() -> int { return 1 } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index fa770795c..1cd26211e 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -9,108 +9,205 @@ import "base:runtime" Handle :: distinct i32 Pid :: distinct i32 File_Time :: distinct u64 -Errno :: distinct i32 INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno: 0 +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, + ENOENT = 2, + ESRCH = 3, + EINTR = 4, + EIO = 5, + ENXIO = 6, + E2BIG = 7, + ENOEXEC = 8, + EBADF = 9, + ECHILD = 10, + EDEADLK = 11, + ENOMEM = 12, + EACCES = 13, + EFAULT = 14, + ENOTBLK = 15, + EBUSY = 16, + EEXIST = 17, + EXDEV = 18, + ENODEV = 19, + ENOTDIR = 20, + EISDIR = 21, + EINVAL = 22, + ENFILE = 23, + EMFILE = 24, + ENOTTY = 25, + ETXTBSY = 26, + EFBIG = 27, + ENOSPC = 28, + ESPIPE = 29, + EROFS = 30, + EMLINK = 31, + EPIPE = 32, + EDOM = 33, + ERANGE = 34, + EAGAIN = 35, + EWOULDBLOCK = EAGAIN, + EINPROGRESS = 36, + EALREADY = 37, + ENOTSOCK = 38, + EDESTADDRREQ = 39, + EMSGSIZE = 40, + EPROTOTYPE = 41, + ENOPROTOOPT = 42, + EPROTONOSUPPORT = 43, + ESOCKTNOSUPPORT = 44, + EOPNOTSUPP = 45, + EPFNOSUPPORT = 46, + EAFNOSUPPORT = 47, + EADDRINUSE = 48, + EADDRNOTAVAIL = 49, + ENETDOWN = 50, + ENETUNREACH = 51, + ENETRESET = 52, + ECONNABORTED = 53, + ECONNRESET = 54, + ENOBUFS = 55, + EISCONN = 56, + ENOTCONN = 57, + ESHUTDOWN = 58, + ETOOMANYREFS = 59, + ETIMEDOUT = 60, + ECONNREFUSED = 61, + ELOOP = 62, + ENAMETOOLONG = 63, + EHOSTDOWN = 64, + EHOSTUNREACH = 65, + ENOTEMPTY = 66, + EPROCLIM = 67, + EUSERS = 68, + EDQUOT = 69, + ESTALE = 70, + EREMOTE = 71, + EBADRPC = 72, + ERPCMISMATCH = 73, + EPROGUNAVAIL = 74, + EPROGMISMATCH = 75, + EPROCUNAVAIL = 76, + ENOLCK = 77, + ENOSYS = 78, + EFTYPE = 79, + EAUTH = 80, + ENEEDAUTH = 81, + EIPSEC = 82, + ENOATTR = 83, + EILSEQ = 84, + ENOMEDIUM = 85, + EMEDIUMTYPE = 86, + EOVERFLOW = 87, + ECANCELED = 88, + EIDRM = 89, + ENOMSG = 90, + ENOTSUP = 91, + EBADMSG = 92, + ENOTRECOVERABLE = 93, + EOWNERDEAD = 94, + EPROTO = 95, +} -EPERM: Errno: 1 -ENOENT: Errno: 2 -ESRCH: Errno: 3 -EINTR: Errno: 4 -EIO: Errno: 5 -ENXIO: Errno: 6 -E2BIG: Errno: 7 -ENOEXEC: Errno: 8 -EBADF: Errno: 9 -ECHILD: Errno: 10 -EDEADLK: Errno: 11 -ENOMEM: Errno: 12 -EACCES: Errno: 13 -EFAULT: Errno: 14 -ENOTBLK: Errno: 15 -EBUSY: Errno: 16 -EEXIST: Errno: 17 -EXDEV: Errno: 18 -ENODEV: Errno: 19 -ENOTDIR: Errno: 20 -EISDIR: Errno: 21 -EINVAL: Errno: 22 -ENFILE: Errno: 23 -EMFILE: Errno: 24 -ENOTTY: Errno: 25 -ETXTBSY: Errno: 26 -EFBIG: Errno: 27 -ENOSPC: Errno: 28 -ESPIPE: Errno: 29 -EROFS: Errno: 30 -EMLINK: Errno: 31 -EPIPE: Errno: 32 -EDOM: Errno: 33 -ERANGE: Errno: 34 -EAGAIN: Errno: 35 -EWOULDBLOCK: Errno: EAGAIN -EINPROGRESS: Errno: 36 -EALREADY: Errno: 37 -ENOTSOCK: Errno: 38 -EDESTADDRREQ: Errno: 39 -EMSGSIZE: Errno: 40 -EPROTOTYPE: Errno: 41 -ENOPROTOOPT: Errno: 42 -EPROTONOSUPPORT: Errno: 43 -ESOCKTNOSUPPORT: Errno: 44 -EOPNOTSUPP: Errno: 45 -EPFNOSUPPORT: Errno: 46 -EAFNOSUPPORT: Errno: 47 -EADDRINUSE: Errno: 48 -EADDRNOTAVAIL: Errno: 49 -ENETDOWN: Errno: 50 -ENETUNREACH: Errno: 51 -ENETRESET: Errno: 52 -ECONNABORTED: Errno: 53 -ECONNRESET: Errno: 54 -ENOBUFS: Errno: 55 -EISCONN: Errno: 56 -ENOTCONN: Errno: 57 -ESHUTDOWN: Errno: 58 -ETOOMANYREFS: Errno: 59 -ETIMEDOUT: Errno: 60 -ECONNREFUSED: Errno: 61 -ELOOP: Errno: 62 -ENAMETOOLONG: Errno: 63 -EHOSTDOWN: Errno: 64 -EHOSTUNREACH: Errno: 65 -ENOTEMPTY: Errno: 66 -EPROCLIM: Errno: 67 -EUSERS: Errno: 68 -EDQUOT: Errno: 69 -ESTALE: Errno: 70 -EREMOTE: Errno: 71 -EBADRPC: Errno: 72 -ERPCMISMATCH: Errno: 73 -EPROGUNAVAIL: Errno: 74 -EPROGMISMATCH: Errno: 75 -EPROCUNAVAIL: Errno: 76 -ENOLCK: Errno: 77 -ENOSYS: Errno: 78 -EFTYPE: Errno: 79 -EAUTH: Errno: 80 -ENEEDAUTH: Errno: 81 -EIPSEC: Errno: 82 -ENOATTR: Errno: 83 -EILSEQ: Errno: 84 -ENOMEDIUM: Errno: 85 -EMEDIUMTYPE: Errno: 86 -EOVERFLOW: Errno: 87 -ECANCELED: Errno: 88 -EIDRM: Errno: 89 -ENOMSG: Errno: 90 -ENOTSUP: Errno: 91 -EBADMSG: Errno: 92 -ENOTRECOVERABLE: Errno: 93 -EOWNERDEAD: Errno: 94 -EPROTO: Errno: 95 +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +E2BIG :: Platform_Error.E2BIG +ENOEXEC :: Platform_Error.ENOEXEC +EBADF :: Platform_Error.EBADF +ECHILD :: Platform_Error.ECHILD +EDEADLK :: Platform_Error.EDEADLK +ENOMEM :: Platform_Error.ENOMEM +EACCES :: Platform_Error.EACCES +EFAULT :: Platform_Error.EFAULT +ENOTBLK :: Platform_Error.ENOTBLK +EBUSY :: Platform_Error.EBUSY +EEXIST :: Platform_Error.EEXIST +EXDEV :: Platform_Error.EXDEV +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ENOTTY :: Platform_Error.ENOTTY +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EMLINK :: Platform_Error.EMLINK +EPIPE :: Platform_Error.EPIPE +EDOM :: Platform_Error.EDOM +ERANGE :: Platform_Error.ERANGE +EAGAIN :: Platform_Error.EAGAIN +EWOULDBLOCK :: Platform_Error.EWOULDBLOCK +EINPROGRESS :: Platform_Error.EINPROGRESS +EALREADY :: Platform_Error.EALREADY +ENOTSOCK :: Platform_Error.ENOTSOCK +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ +EMSGSIZE :: Platform_Error.EMSGSIZE +EPROTOTYPE :: Platform_Error.EPROTOTYPE +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT +EADDRINUSE :: Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL +ENETDOWN :: Platform_Error.ENETDOWN +ENETUNREACH :: Platform_Error.ENETUNREACH +ENETRESET :: Platform_Error.ENETRESET +ECONNABORTED :: Platform_Error.ECONNABORTED +ECONNRESET :: Platform_Error.ECONNRESET +ENOBUFS :: Platform_Error.ENOBUFS +EISCONN :: Platform_Error.EISCONN +ENOTCONN :: Platform_Error.ENOTCONN +ESHUTDOWN :: Platform_Error.ESHUTDOWN +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS +ETIMEDOUT :: Platform_Error.ETIMEDOUT +ECONNREFUSED :: Platform_Error.ECONNREFUSED +ELOOP :: Platform_Error.ELOOP +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG +EHOSTDOWN :: Platform_Error.EHOSTDOWN +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH +ENOTEMPTY :: Platform_Error.ENOTEMPTY +EPROCLIM :: Platform_Error.EPROCLIM +EUSERS :: Platform_Error.EUSERS +EDQUOT :: Platform_Error.EDQUOT +ESTALE :: Platform_Error.ESTALE +EREMOTE :: Platform_Error.EREMOTE +EBADRPC :: Platform_Error.EBADRPC +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL +ENOLCK :: Platform_Error.ENOLCK +ENOSYS :: Platform_Error.ENOSYS +EFTYPE :: Platform_Error.EFTYPE +EAUTH :: Platform_Error.EAUTH +ENEEDAUTH :: Platform_Error.ENEEDAUTH +EIPSEC :: Platform_Error.EIPSEC +ENOATTR :: Platform_Error.ENOATTR +EILSEQ :: Platform_Error.EILSEQ +ENOMEDIUM :: Platform_Error.ENOMEDIUM +EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE +EOVERFLOW :: Platform_Error.EOVERFLOW +ECANCELED :: Platform_Error.ECANCELED +EIDRM :: Platform_Error.EIDRM +ENOMSG :: Platform_Error.ENOMSG +ENOTSUP :: Platform_Error.ENOTSUP +EBADMSG :: Platform_Error.EBADMSG +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: Platform_Error.EOWNERDEAD +EPROTO :: Platform_Error.EPROTO O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 @@ -225,13 +322,13 @@ S_ISUID :: 0o4000 // Set user id on execution S_ISGID :: 0o2000 // Set group id on execution S_ISTXT :: 0o1000 // Sticky bit -S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } F_OK :: 0x00 // Test for file existance X_OK :: 0x01 // Test for execute permission @@ -291,38 +388,47 @@ foreign libc { @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } -get_last_error :: proc "contextless" () -> int { - return int(__error()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) } -fork :: proc() -> (Pid, Errno) { +@(require_results) +fork :: proc() -> (Pid, Error) { pid := _unix_fork() if pid == -1 { - return Pid(-1), Errno(get_last_error()) + return Pid(-1), get_last_error() } - return Pid(pid), ERROR_NONE + return Pid(pid), nil } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) handle := _unix_open(cstr, c.int(flags), c.int(mode)) if handle == -1 { - return INVALID_HANDLE, Errno(get_last_error()) + return INVALID_HANDLE, get_last_error() } - return handle, ERROR_NONE + return handle, nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { result := _unix_close(fd) if result == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil +} + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil } // If you read or write more than `SSIZE_MAX` bytes, OpenBSD returns `EINVAL`. @@ -333,124 +439,148 @@ close :: proc(fd: Handle) -> Errno { @(private) MAX_RW :: 1 << 30 -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { +read :: proc(fd: Handle, data: []byte) -> (int, Error) { to_read := min(c.size_t(len(data)), MAX_RW) bytes_read := _unix_read(fd, &data[0], to_read) if bytes_read == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_read), ERROR_NONE + return int(bytes_read), nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } to_write := min(c.size_t(len(data)), MAX_RW) bytes_written := _unix_write(fd, &data[0], to_write) if bytes_written == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_written), ERROR_NONE + return int(bytes_written), nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = read(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr := seek(fd, offset, SEEK_CUR) or_return + n, err = write(fd, data) + _, err1 := seek(fd, curr, SEEK_SET) + if err1 != nil && err == nil { + err = err1 + } + return +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return res, ERROR_NONE + return res, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return -1, err - } - return s.size, ERROR_NONE +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return } -rename :: proc(old_path, new_path: string) -> Errno { +rename :: proc(old_path, new_path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) res := _unix_rename(old_path_cstr, new_path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_unlink(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_mkdir(path_cstr, mode) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove_directory :: proc(path: string) -> Errno { +remove_directory :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_rmdir(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -469,26 +599,22 @@ stderr: Handle = 2 last_write_time :: proc(fd: Handle) -> File_Time {} last_write_time_by_name :: proc(name: string) -> File_Time {} */ -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { - s, err := _stat(name) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -496,13 +622,13 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_stat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -510,55 +636,55 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_lstat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized s: OS_Stat = --- res := _unix_fstat(fd, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE + err = nil if result == nil { end_of_stream = true @@ -568,8 +694,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -579,23 +705,25 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { bufsz += MAX_PATH delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } } // XXX OpenBSD -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { - return "", Errno(ENOSYS) +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + return "", Error(ENOSYS) } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -606,25 +734,26 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } defer _unix_free(path_ptr) path = strings.clone(string(cstring(path_ptr))) - return path, ERROR_NONE + return path, nil } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_access(cstr, c.int(mask)) if res == -1 { - return false, Errno(get_last_error()) + return false, get_last_error() } - return true, ERROR_NONE + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) @@ -635,11 +764,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) get_current_directory :: proc() -> string { buf := make([dynamic]u8, MAX_PATH) for { @@ -647,7 +778,7 @@ get_current_directory :: proc() -> string { if cwd != nil { return string(cwd) } - if Errno(get_last_error()) != ERANGE { + if get_last_error() != ERANGE { delete(buf) return "" } @@ -656,14 +787,14 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -671,16 +802,19 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(c.int(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return _unix_getthrid() } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) handle := _unix_dlopen(cstr, c.int(flags)) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -692,11 +826,13 @@ dlclose :: proc(handle: rawptr) -> bool { assert(handle != nil) return _unix_dlclose(handle) == 0 } +@(require_results) dlerror :: proc() -> string { return string(_unix_dlerror()) } -get_page_size :: proc "contextless" () -> int { +@(require_results) +get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @static page_size := -1 @@ -710,11 +846,12 @@ get_page_size :: proc "contextless" () -> int { _SC_NPROCESSORS_ONLN :: 503 -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { return int(_sysconf(_SC_NPROCESSORS_ONLN)) } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index 8a1acb194..28f470357 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -4,12 +4,10 @@ import "core:sys/wasm/wasi" import "base:runtime" Handle :: distinct i32 -Errno :: distinct i32 +_Platform_Error :: wasi.errno_t INVALID_HANDLE :: -1 -ERROR_NONE :: Errno(wasi.errno_t.SUCCESS) - O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 O_RDWR :: 0x00002 @@ -26,10 +24,10 @@ O_CLOEXEC :: 0x80000 stdin: Handle = 0 stdout: Handle = 1 stderr: Handle = 2 -current_dir: Handle = 3 args := _alloc_command_line_arguments() +@(require_results) _alloc_command_line_arguments :: proc() -> (args: []string) { args = make([]string, len(runtime.args__)) for &arg, i in args { @@ -38,26 +36,136 @@ _alloc_command_line_arguments :: proc() -> (args: []string) { return } +// WASI works with "preopened" directories, the environment retrieves directories +// (for example with `wasmtime --dir=. module.wasm`) and those given directories +// are the only ones accessible by the application. +// +// So in order to facilitate the `os` API (absolute paths etc.) we keep a list +// of the given directories and match them when needed (notably `os.open`). + +@(private) +Preopen :: struct { + fd: wasi.fd_t, + prefix: string, +} +@(private) +preopens: []Preopen + +@(init, private) +init_preopens :: proc() { + + strip_prefixes :: proc(path: string) -> string { + path := path + loop: for len(path) > 0 { + switch { + case path[0] == '/': + path = path[1:] + case len(path) > 2 && path[0] == '.' && path[1] == '/': + path = path[2:] + case len(path) == 1 && path[0] == '.': + path = path[1:] + case: + break loop + } + } + return path + } + + dyn_preopens: [dynamic]Preopen + loop: for fd := wasi.fd_t(3); ; fd += 1 { + desc, err := wasi.fd_prestat_get(fd) + #partial switch err { + case .BADF: break loop + case: panic("fd_prestat_get returned an unexpected error") + case .SUCCESS: + } + + switch desc.tag { + case .DIR: + buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens") + if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { + panic("could not get filesystem preopen dir name") + } + append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))}) + } + } + preopens = dyn_preopens[:] +} + +@(require_results) +wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { + @(require_results) + prefix_matches :: proc(prefix, path: string) -> bool { + // Empty is valid for any relative path. + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { + return true + } + + if len(path) < len(prefix) { + return false + } + + if path[:len(prefix)] != prefix { + return false + } + + // Only match on full components. + i := len(prefix) + for i > 0 && prefix[i-1] == '/' { + i -= 1 + } + return path[i] == '/' + } + + path := path + for len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + match: Preopen + #reverse for preopen in preopens { + if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { + match = preopen + } + } + + if match.fd == 0 { + return 0, "", false + } + + relative := path[len(match.prefix):] + for len(relative) > 0 && relative[0] == '/' { + relative = relative[1:] + } + + if len(relative) == 0 { + relative = "." + } + + return match.fd, relative, true +} + write :: proc(fd: Handle, data: []byte) -> (int, Errno) { iovs := wasi.ciovec_t(data) n, err := wasi.fd_write(wasi.fd_t(fd), {iovs}) - return int(n), Errno(err) + return int(n), Platform_Error(err) } read :: proc(fd: Handle, data: []byte) -> (int, Errno) { iovs := wasi.iovec_t(data) n, err := wasi.fd_read(wasi.fd_t(fd), {iovs}) - return int(n), Errno(err) + return int(n), Platform_Error(err) } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { iovs := wasi.ciovec_t(data) n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) - return int(n), Errno(err) + return int(n), Platform_Error(err) } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { iovs := wasi.iovec_t(data) n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) - return int(n), Errno(err) + return int(n), Platform_Error(err) } +@(require_results) open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { oflags: wasi.oflags_t if mode & O_CREATE == O_CREATE { @@ -87,31 +195,43 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn if mode & O_SYNC == O_SYNC { fdflags += {.SYNC} } - fd, err := wasi.path_open(wasi.fd_t(current_dir),{.SYMLINK_FOLLOW},path,oflags,rights,{},fdflags) - return Handle(fd), Errno(err) + + dir_fd, relative, ok := wasi_match_preopen(path) + if !ok { + return INVALID_HANDLE, Errno(wasi.errno_t.BADF) + } + + fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) + return Handle(fd), Platform_Error(err) } close :: proc(fd: Handle) -> Errno { err := wasi.fd_close(wasi.fd_t(fd)) - return Errno(err) + return Platform_Error(err) } + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil +} + seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence)) - return i64(n), Errno(err) + return i64(n), Platform_Error(err) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return 0 } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { return 1 } -file_size :: proc(fd: Handle) -> (i64, Errno) { - stat, err := wasi.fd_filestat_get(wasi.fd_t(fd)) - if err != nil { - return 0, Errno(err) - } - return i64(stat.size), 0 +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Errno) { + stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return + size = i64(stat.size) + return } diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 0f3b2a34b..273fe5af0 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -7,7 +7,6 @@ import "base:intrinsics" Handle :: distinct uintptr File_Time :: distinct u64 -Errno :: distinct int INVALID_HANDLE :: ~Handle(0) @@ -27,71 +26,120 @@ O_SYNC :: 0x01000 O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 +_Platform_Error :: win32.System_Error -ERROR_NONE: Errno : 0 -ERROR_FILE_NOT_FOUND: Errno : 2 -ERROR_PATH_NOT_FOUND: Errno : 3 -ERROR_ACCESS_DENIED: Errno : 5 -ERROR_INVALID_HANDLE: Errno : 6 -ERROR_NOT_ENOUGH_MEMORY: Errno : 8 -ERROR_NO_MORE_FILES: Errno : 18 -ERROR_HANDLE_EOF: Errno : 38 -ERROR_NETNAME_DELETED: Errno : 64 -ERROR_FILE_EXISTS: Errno : 80 -ERROR_INVALID_PARAMETER: Errno : 87 -ERROR_BROKEN_PIPE: Errno : 109 -ERROR_BUFFER_OVERFLOW: Errno : 111 -ERROR_INSUFFICIENT_BUFFER: Errno : 122 -ERROR_MOD_NOT_FOUND: Errno : 126 -ERROR_PROC_NOT_FOUND: Errno : 127 -ERROR_DIR_NOT_EMPTY: Errno : 145 -ERROR_ALREADY_EXISTS: Errno : 183 -ERROR_ENVVAR_NOT_FOUND: Errno : 203 -ERROR_MORE_DATA: Errno : 234 -ERROR_OPERATION_ABORTED: Errno : 995 -ERROR_IO_PENDING: Errno : 997 -ERROR_NOT_FOUND: Errno : 1168 -ERROR_PRIVILEGE_NOT_HELD: Errno : 1314 -WSAEACCES: Errno : 10013 -WSAECONNRESET: Errno : 10054 +ERROR_FILE_NOT_FOUND :: _Platform_Error(2) +ERROR_PATH_NOT_FOUND :: _Platform_Error(3) +ERROR_ACCESS_DENIED :: _Platform_Error(5) +ERROR_INVALID_HANDLE :: _Platform_Error(6) +ERROR_NOT_ENOUGH_MEMORY :: _Platform_Error(8) +ERROR_NO_MORE_FILES :: _Platform_Error(18) +ERROR_HANDLE_EOF :: _Platform_Error(38) +ERROR_NETNAME_DELETED :: _Platform_Error(64) +ERROR_FILE_EXISTS :: _Platform_Error(80) +ERROR_INVALID_PARAMETER :: _Platform_Error(87) +ERROR_BROKEN_PIPE :: _Platform_Error(109) +ERROR_BUFFER_OVERFLOW :: _Platform_Error(111) +ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122) +ERROR_MOD_NOT_FOUND :: _Platform_Error(126) +ERROR_PROC_NOT_FOUND :: _Platform_Error(127) +ERROR_DIR_NOT_EMPTY :: _Platform_Error(145) +ERROR_ALREADY_EXISTS :: _Platform_Error(183) +ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203) +ERROR_MORE_DATA :: _Platform_Error(234) +ERROR_OPERATION_ABORTED :: _Platform_Error(995) +ERROR_IO_PENDING :: _Platform_Error(997) +ERROR_NOT_FOUND :: _Platform_Error(1168) +ERROR_PRIVILEGE_NOT_HELD :: _Platform_Error(1314) +WSAEACCES :: _Platform_Error(10013) +WSAECONNRESET :: _Platform_Error(10054) -// Windows reserves errors >= 1<<29 for application use -ERROR_FILE_IS_PIPE: Errno : 1<<29 + 0 -ERROR_FILE_IS_NOT_DIR: Errno : 1<<29 + 1 -ERROR_NEGATIVE_OFFSET: Errno : 1<<29 + 2 +ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe +ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir // "Argv" arguments converted to Odin strings args := _alloc_command_line_arguments() +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + err := win32.GetLastError() + if err == 0 { + return nil + } + switch err { + case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION: + return .Permission_Denied + + case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS: + return .Exist + + case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND: + return .Not_Exist + + case win32.ERROR_NO_DATA: + return .Closed + + case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT: + return .Timeout + + case win32.ERROR_NOT_SUPPORTED: + return .Unsupported + + case win32.ERROR_HANDLE_EOF: + return .EOF + + case win32.ERROR_INVALID_HANDLE: + return .Invalid_File + + case + win32.ERROR_BAD_ARGUMENTS, + win32.ERROR_INVALID_PARAMETER, + win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_NO_MORE_FILES, + win32.ERROR_LOCK_VIOLATION, + win32.ERROR_BROKEN_PIPE, + win32.ERROR_CALL_NOT_IMPLEMENTED, + win32.ERROR_INSUFFICIENT_BUFFER, + win32.ERROR_INVALID_NAME, + win32.ERROR_LOCK_FAILED, + win32.ERROR_ENVVAR_NOT_FOUND, + win32.ERROR_OPERATION_ABORTED, + win32.ERROR_IO_PENDING, + win32.ERROR_NO_UNICODE_TRANSLATION: + // fallthrough + } + return Platform_Error(err) +} - - -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { file_info: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(win32.HANDLE(fd), &file_info) { - return 0, Errno(win32.GetLastError()) + return 0, get_last_error() } lo := File_Time(file_info.ftLastWriteTime.dwLowDateTime) hi := File_Time(file_info.ftLastWriteTime.dwHighDateTime) - return lo | hi << 32, ERROR_NONE + return lo | hi << 32, nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { data: win32.WIN32_FILE_ATTRIBUTE_DATA wide_path := win32.utf8_to_wstring(name) if !win32.GetFileAttributesExW(wide_path, win32.GetFileExInfoStandard, &data) { - return 0, Errno(win32.GetLastError()) + return 0, get_last_error() } l := File_Time(data.ftLastWriteTime.dwLowDateTime) h := File_Time(data.ftLastWriteTime.dwHighDateTime) - return l | h << 32, ERROR_NONE + return l | h << 32, nil } -get_page_size :: proc "contextless" () -> int { +@(require_results) +get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @static page_size := -1 @@ -105,7 +153,7 @@ get_page_size :: proc "contextless" () -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { length : win32.DWORD = 0 result := win32.GetLogicalProcessorInformation(nil, &length) @@ -136,12 +184,14 @@ exit :: proc "contextless" (code: int) -> ! { +@(require_results) current_thread_id :: proc "contextless" () -> int { return int(win32.GetCurrentThreadId()) } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { arg_count: i32 arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count) @@ -175,44 +225,52 @@ _alloc_command_line_arguments :: proc() -> []string { */ WINDOWS_11_BUILD_CUTOFF :: 22_000 -get_windows_version_w :: proc() -> win32.OSVERSIONINFOEXW { +@(require_results) +get_windows_version_w :: proc "contextless" () -> win32.OSVERSIONINFOEXW { osvi : win32.OSVERSIONINFOEXW osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW) win32.RtlGetVersion(&osvi) return osvi } -is_windows_xp :: proc() -> bool { +@(require_results) +is_windows_xp :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) } -is_windows_vista :: proc() -> bool { +@(require_results) +is_windows_vista :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) } -is_windows_7 :: proc() -> bool { +@(require_results) +is_windows_7 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) } -is_windows_8 :: proc() -> bool { +@(require_results) +is_windows_8 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) } -is_windows_8_1 :: proc() -> bool { +@(require_results) +is_windows_8_1 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) } -is_windows_10 :: proc() -> bool { +@(require_results) +is_windows_10 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < WINDOWS_11_BUILD_CUTOFF) } -is_windows_11 :: proc() -> bool { +@(require_results) +is_windows_11 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF) } diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 3bd62dfc7..8e89bee4f 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -50,14 +50,14 @@ File_Info :: struct { } */ -@private +@(private, require_results) _make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time { return time.Time{ _nsec = uft.nanoseconds + uft.seconds * 1_000_000_000, } } -@private +@(private) _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) { fi.size = s.size fi.mode = cast(File_Mode)s.mode @@ -71,7 +71,7 @@ _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) { } -@private +@(private, require_results) path_base :: proc(path: string) -> string { is_separator :: proc(c: byte) -> bool { return c == '/' @@ -100,55 +100,35 @@ path_base :: proc(path: string) -> string { } -lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { - +@(require_results) +lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { context.allocator = allocator - s: OS_Stat - s, err = _lstat(name) - if err != ERROR_NONE { - return fi, err - } + s := _lstat(name) or_return _fill_file_info_from_stat(&fi, s) - fi.fullpath, err = absolute_path_from_relative(name) - if err != ERROR_NONE { - return - } + fi.fullpath = absolute_path_from_relative(name) or_return fi.name = path_base(fi.fullpath) - return fi, ERROR_NONE + return } -stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { +@(require_results) +stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { context.allocator = allocator - s: OS_Stat - s, err = _stat(name) - if err != ERROR_NONE { - return fi, err - } + s := _stat(name) or_return _fill_file_info_from_stat(&fi, s) - fi.fullpath, err = absolute_path_from_relative(name) - if err != ERROR_NONE { - return - } + fi.fullpath = absolute_path_from_relative(name) or_return fi.name = path_base(fi.fullpath) - return fi, ERROR_NONE + return } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) { - +@(require_results) +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Error) { context.allocator = allocator - s: OS_Stat - s, err = _fstat(fd) - if err != ERROR_NONE { - return fi, err - } + s := _fstat(fd) or_return _fill_file_info_from_stat(&fi, s) - fi.fullpath, err = absolute_path_from_handle(fd) - if err != ERROR_NONE { - return - } + fi.fullpath = absolute_path_from_handle(fd) or_return fi.name = path_base(fi.fullpath) - return fi, ERROR_NONE + return } diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index 4bb3bd4c4..ca4f87668 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -4,7 +4,7 @@ import "core:time" import "base:runtime" import win32 "core:sys/windows" -@(private) +@(private, require_results) full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) { context.allocator = allocator @@ -19,10 +19,10 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa for { n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { - return "", Errno(win32.GetLastError()) + return "", get_last_error() } if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE + return win32.utf16_to_utf8(buf[:n], allocator) or_else "", nil } resize(&buf, len(buf)*2) } @@ -30,7 +30,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa return } -@(private) +@(private, require_results) _stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) { if len(name) == 0 { return {}, ERROR_PATH_NOT_FOUND @@ -54,7 +54,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al fd: win32.WIN32_FIND_DATAW sh := win32.FindFirstFileW(wname, &fd) if sh == win32.INVALID_HANDLE_VALUE { - e = Errno(win32.GetLastError()) + e = get_last_error() return } win32.FindClose(sh) @@ -64,7 +64,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) if h == win32.INVALID_HANDLE_VALUE { - e = Errno(win32.GetLastError()) + e = get_last_error() return } defer win32.CloseHandle(h) @@ -72,26 +72,29 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al } +@(require_results) lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { attrs := win32.FILE_FLAG_BACKUP_SEMANTICS attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT return _stat(name, attrs, allocator) } +@(require_results) stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { attrs := win32.FILE_FLAG_BACKUP_SEMANTICS return _stat(name, attrs, allocator) } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, errno: Errno) { +@(require_results) +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) { if fd == 0 { - return {}, ERROR_INVALID_HANDLE + err = ERROR_INVALID_HANDLE } context.allocator = allocator - path, err := cleanpath_from_handle(fd) - if err != ERROR_NONE { - return {}, err + path := cleanpath_from_handle(fd) or_return + defer if err != nil { + delete(path) } h := win32.HANDLE(fd) @@ -99,16 +102,16 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: fi.name = basename(path) fi.mode |= file_type_mode(h) - errno = ERROR_NONE + err = nil case: - fi, errno = file_info_from_get_file_information_by_handle(path, h) + fi = file_info_from_get_file_information_by_handle(path, h) or_return } fi.fullpath = path return } -@(private) +@(private, require_results) cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { buf := buf N := 0 @@ -133,16 +136,13 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { return buf } -@(private) -cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) { +@(private, require_results) +cleanpath_from_handle :: proc(fd: Handle) -> (s: string, err: Errno) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - buf, err := cleanpath_from_handle_u16(fd, context.temp_allocator) - if err != 0 { - return "", err - } - return win32.utf16_to_utf8(buf, context.allocator) or_else "", err + buf := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return + return win32.utf16_to_utf8(buf, context.allocator) } -@(private) +@(private, require_results) cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) { if fd == 0 { return nil, ERROR_INVALID_HANDLE @@ -151,20 +151,20 @@ cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ( n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) if n == 0 { - return nil, Errno(win32.GetLastError()) + return nil, get_last_error() } buf := make([]u16, max(n, win32.DWORD(260))+1, allocator) buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0) - return buf[:buf_len], ERROR_NONE + return buf[:buf_len], nil } -@(private) +@(private, require_results) cleanpath_from_buf :: proc(buf: []u16) -> string { buf := buf buf = cleanpath_strip_prefix(buf) return win32.utf16_to_utf8(buf, context.allocator) or_else "" } -@(private) +@(private, require_results) basename :: proc(name: string) -> (base: string) { name := name if len(name) > 3 && name[:3] == `\\?` { @@ -190,7 +190,7 @@ basename :: proc(name: string) -> (base: string) { return name } -@(private) +@(private, require_results) file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE: @@ -202,7 +202,7 @@ file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { } -@(private) +@(private, require_results) file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { mode |= 0o444 @@ -239,7 +239,7 @@ windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) { fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) } -@(private) +@(private, require_results) file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) @@ -254,7 +254,7 @@ file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_ return } -@(private) +@(private, require_results) file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) @@ -269,20 +269,20 @@ file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) return } -@(private) +@(private, require_results) file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { - err := Errno(win32.GetLastError()) + err := get_last_error() return {}, err } ti: win32.FILE_ATTRIBUTE_TAG_INFO if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { - err := win32.GetLastError() - if err != u32(ERROR_INVALID_PARAMETER) { - return {}, Errno(err) + err := get_last_error() + if err != ERROR_INVALID_PARAMETER { + return {}, err } // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 @@ -299,5 +299,5 @@ file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HAN windows_set_file_info_times(&fi, &d) - return fi, ERROR_NONE + return fi, nil } diff --git a/core/os/stream.odin b/core/os/stream.odin index 9a168b95c..8acbee489 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -14,44 +14,36 @@ stream_from_handle :: proc(fd: Handle) -> io.Stream { _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { fd := Handle(uintptr(stream_data)) n_int: int - os_err: Errno + os_err: Error switch mode { case .Close: - close(fd) + os_err = close(fd) case .Flush: - when ODIN_OS == .Windows { - flush(fd) - } else { - // TOOD(bill): other operating systems - } + os_err = flush(fd) case .Read: n_int, os_err = read(fd, p) n = i64(n_int) - if n == 0 && os_err == 0 { + if n == 0 && os_err == nil { err = .EOF } case .Read_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { - n_int, os_err = read_at(fd, p, offset) - n = i64(n_int) - if n == 0 && os_err == 0 { - err = .EOF - } + n_int, os_err = read_at(fd, p, offset) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF } case .Write: n_int, os_err = write(fd, p) n = i64(n_int) - if n == 0 && os_err == 0 { + if n == 0 && os_err == nil { err = .EOF } case .Write_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { - n_int, os_err = write_at(fd, p, offset) - n = i64(n_int) - if n == 0 && os_err == 0 { - err = .EOF - } + n_int, os_err = write_at(fd, p, offset) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF } case .Seek: n, os_err = seek(fd, offset, int(whence)) @@ -60,20 +52,11 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Destroy: err = .Empty case .Query: - when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { - return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query}) - } else { - return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) - } + return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) } - if err == nil && os_err != 0 { - when ODIN_OS == .Windows { - if os_err == ERROR_HANDLE_EOF { - return n, .EOF - } - } - err = .Unknown + if err == nil && os_err != nil { + err = error_to_io_error(os_err) } return } diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 1279bdd84..7eb72b9a7 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -271,7 +271,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont d, derr := os.open(dir, os.O_RDONLY) - if derr != 0 { + if derr != nil { return } defer os.close(d) @@ -280,7 +280,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont file_info, ferr := os.fstat(d) defer os.file_info_delete(file_info) - if ferr != 0 { + if ferr != nil { return } if !file_info.is_dir { diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 5ebd2cdc2..0dcb28cf8 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -52,7 +52,7 @@ is_abs :: proc(path: string) -> bool { @(private) -temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { +temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { ta := context.temp_allocator name := name @@ -63,17 +63,17 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { p := win32.utf8_to_utf16(name, ta) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) if n == 0 { - return "", os.Errno(win32.GetLastError()) + return "", os.get_last_error() } buf := make([]u16, n, ta) n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { delete(buf) - return "", os.Errno(win32.GetLastError()) + return "", os.get_last_error() } - return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE + return win32.utf16_to_utf8(buf[:n], ta) } @@ -81,7 +81,7 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) full_path, err := temp_full_path(path) - if err != 0 { + if err != nil { return "", false } p := clean(full_path, allocator) diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 9ba3165dc..51dfa71d2 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -14,7 +14,7 @@ import "core:slice" // The sole exception is if 'skip_dir' is returned as true: // when 'skip_dir' is invoked on a directory. 'walk' skips directory contents // when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory -Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr) -> (err: os.Errno, skip_dir: bool) +Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Error, user_data: rawptr) -> (err: os.Error, skip_dir: bool) // walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root' // All errors that happen visiting files and directories are filtered by walk_proc @@ -22,44 +22,44 @@ Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr) // NOTE: Walking large directories can be inefficient due to the lexical sort // NOTE: walk does not follow symbolic links // NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done -walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Errno { +walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Error { info, err := os.lstat(root, context.temp_allocator) defer os.file_info_delete(info, context.temp_allocator) skip_dir: bool - if err != 0 { + if err != nil { err, skip_dir = walk_proc(info, err, user_data) } else { err, skip_dir = _walk(info, walk_proc, user_data) } - return 0 if skip_dir else err + return nil if skip_dir else err } @(private) -_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Errno, skip_dir: bool) { +_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Error, skip_dir: bool) { if !info.is_dir { if info.fullpath == "" && info.name == "" { // ignore empty things return } - return walk_proc(info, 0, user_data) + return walk_proc(info, nil, user_data) } fis: []os.File_Info - err1: os.Errno + err1: os.Error fis, err = read_dir(info.fullpath, context.temp_allocator) defer os.file_info_slice_delete(fis, context.temp_allocator) err1, skip_dir = walk_proc(info, err, user_data) - if err != 0 || err1 != 0 || skip_dir { + if err != nil || err1 != nil || skip_dir { err = err1 return } for fi in fis { err, skip_dir = _walk(fi, walk_proc, user_data) - if err != 0 || skip_dir { + if err != nil || skip_dir { if !fi.is_dir || !skip_dir { return } @@ -70,19 +70,12 @@ _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (e } @(private) -read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> ([]os.File_Info, os.Errno) { - f, err := os.open(dir_name, os.O_RDONLY) - if err != 0 { - return nil, err - } - fis: []os.File_Info - fis, err = os.read_dir(f, -1, allocator) - os.close(f) - if err != 0 { - return nil, err - } +read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> (fis: []os.File_Info, err: os.Error) { + f := os.open(dir_name, os.O_RDONLY) or_return + defer os.close(f) + fis = os.read_dir(f, -1, allocator) or_return slice.sort_by(fis, proc(a, b: os.File_Info) -> bool { return a.name < b.name }) - return fis, 0 + return } diff --git a/core/prof/spall/doc.odin b/core/prof/spall/doc.odin index c34ba0d5b..d9259465b 100644 --- a/core/prof/spall/doc.odin +++ b/core/prof/spall/doc.odin @@ -1,8 +1,10 @@ /* + import "base:runtime" import "core:prof/spall" + import "core:sync" spall_ctx: spall.Context - spall_buffer: spall.Buffer + @(thread_local) spall_buffer: spall.Buffer foo :: proc() { spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure) @@ -13,7 +15,7 @@ defer spall.context_destroy(&spall_ctx) buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE) - spall_buffer = spall.buffer_create(buffer_backing) + spall_buffer = spall.buffer_create(buffer_backing, u32(sync.current_thread_id())) defer spall.buffer_destroy(&spall_ctx, &spall_buffer) spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure) diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index a6fc59e74..12f082b2c 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -68,7 +68,7 @@ BUFFER_DEFAULT_SIZE :: 0x10_0000 context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok { fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600) - if err != os.ERROR_NONE { + if err != nil { return } @@ -227,7 +227,7 @@ _buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_ch } @(no_instrumentation) -write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Errno) { +write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Error) { return _write(fd, buf) } diff --git a/core/prof/spall/spall_linux.odin b/core/prof/spall/spall_linux.odin index 3f475c5e0..b25d2b336 100644 --- a/core/prof/spall/spall_linux.odin +++ b/core/prof/spall/spall_linux.odin @@ -10,21 +10,12 @@ import "core:sys/linux" MAX_RW :: 0x7fffffff @(no_instrumentation) -_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ { - if len(data) == 0 { - return 0, os.ERROR_NONE - } - +_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { for n < len(data) { chunk := data[:min(len(data), MAX_RW)] - written, errno := linux.write(linux.Fd(fd), chunk) - if errno != .NONE { - return n, os.Errno(errno) - } - n += written + n += linux.write(linux.Fd(fd), chunk) or_return } - - return n, os.ERROR_NONE + return } CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index e6199d86b..174b3a11b 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -22,29 +22,24 @@ foreign libc { @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 --- } -@(no_instrumentation) -get_last_error :: proc "contextless" () -> int { - return int(__error()^) -} - MAX_RW :: 0x7fffffff @(no_instrumentation) -_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ { +_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { if len(data) == 0 { - return 0, os.ERROR_NONE + return 0, nil } for n < len(data) { chunk := data[:min(len(data), MAX_RW)] written := _unix_write(fd, raw_data(chunk), len(chunk)) if written < 0 { - return n, os.Errno(get_last_error()) + return n, os.get_last_error() } n += written } - return n, os.ERROR_NONE + return n, nil } CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. diff --git a/core/prof/spall/spall_windows.odin b/core/prof/spall/spall_windows.odin index 4d96c111a..c8b044963 100644 --- a/core/prof/spall/spall_windows.odin +++ b/core/prof/spall/spall_windows.odin @@ -10,9 +10,9 @@ import win32 "core:sys/windows" MAX_RW :: 1<<30 @(no_instrumentation) -_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ { +_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { if len(data) == 0 { - return 0, os.ERROR_NONE + return 0, nil } single_write_length: win32.DWORD @@ -25,12 +25,11 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Errno) #n e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) if single_write_length <= 0 || !e { - err := os.Errno(win32.GetLastError()) - return int(total_write), err + return int(total_write), os.get_last_error() } total_write += i64(single_write_length) } - return int(total_write), os.ERROR_NONE + return int(total_write), nil } @(no_instrumentation) diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index de7379ecc..23c0f803e 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -143,7 +143,7 @@ when !ODIN_NO_RTTI { @(require_results) any_base :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_base(v.id) } return v @@ -151,7 +151,7 @@ any_base :: proc(v: any) -> any { @(require_results) any_core :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_core(v.id) } return v @@ -391,7 +391,7 @@ Struct_Field :: struct { struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - if 0 <= i && i < len(s.names) { + if 0 <= i && i < int(s.field_count) { field.name = s.names[i] field.type = s.types[i] field.tag = Struct_Tag(s.tags[i]) @@ -406,7 +406,7 @@ struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - for fname, i in s.names { + for fname, i in s.names[:s.field_count] { if fname == name { field.name = s.names[i] field.type = s.types[i] @@ -427,7 +427,7 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false) ti := runtime.type_info_base(type_info_of(a.id)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - for name, i in s.names { + for name, i in s.names[:s.field_count] { if name == field { return any{ rawptr(uintptr(a.data) + s.offsets[i]), @@ -463,7 +463,7 @@ struct_field_value :: proc(a: any, field: Struct_Field) -> any { struct_field_names :: proc(T: typeid) -> []string { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.names + return s.names[:s.field_count] } return nil } @@ -472,7 +472,7 @@ struct_field_names :: proc(T: typeid) -> []string { struct_field_types :: proc(T: typeid) -> []^Type_Info { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.types + return s.types[:s.field_count] } return nil } @@ -482,7 +482,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info { struct_field_tags :: proc(T: typeid) -> []Struct_Tag { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return transmute([]Struct_Tag)s.tags + return transmute([]Struct_Tag)s.tags[:s.field_count] } return nil } @@ -491,7 +491,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag { struct_field_offsets :: proc(T: typeid) -> []uintptr { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.offsets + return s.offsets[:s.field_count] } return nil } @@ -501,11 +501,11 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { return soa_zip( - name = s.names, - type = s.types, - tag = transmute([]Struct_Tag)s.tags, - offset = s.offsets, - is_using = s.usings, + name = s.names[:s.field_count], + type = s.types[:s.field_count], + tag = ([^]Struct_Tag)(s.tags)[:s.field_count], + offset = s.offsets[:s.field_count], + is_using = s.usings[:s.field_count], ) } return nil @@ -1409,7 +1409,7 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) { #partial switch info in ti.variant { case Type_Info_Pointer: valid = true - value = a.data + value = (^rawptr)(a.data)^ case Type_Info_String: valid = true @@ -1569,7 +1569,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ if v.equal != nil { return v.equal(a.data, b.data) } else { - for offset, i in v.offsets { + for offset, i in v.offsets[:v.field_count] { x := rawptr(uintptr(a.data) + offset) y := rawptr(uintptr(b.data) + offset) id := v.types[i].id diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 04dd8a52d..4f0674dc8 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -115,16 +115,14 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { case Type_Info_Struct: y := b.variant.(Type_Info_Struct) or_return switch { - case len(x.types) != len(y.types), - x.is_packed != y.is_packed, - x.is_raw_union != y.is_raw_union, - x.custom_align != y.custom_align, + case x.field_count != y.field_count, + x.flags != y.flags, x.soa_kind != y.soa_kind, x.soa_base_type != y.soa_base_type, x.soa_len != y.soa_len: return false } - for _, i in x.types { + for i in 0.. bool { case Type_Info_Bit_Field: y := b.variant.(Type_Info_Bit_Field) or_return if !are_types_identical(x.backing_type, y.backing_type) { return false } - if len(x.names) != len(y.names) { return false } - for _, i in x.names { + if x.field_count != y.field_count { return false } + for _, i in x.names[:x.field_count] { if x.names[i] != y.names[i] { return false } @@ -368,13 +366,13 @@ is_tuple :: proc(info: ^Type_Info) -> bool { is_struct :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) - return ok && !s.is_raw_union + return ok && .raw_union not_in s.flags } @(require_results) is_raw_union :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) - return ok && s.is_raw_union + return ok && .raw_union in s.flags } @(require_results) is_union :: proc(info: ^Type_Info) -> bool { @@ -495,7 +493,7 @@ write_type_builder :: proc(buf: ^strings.Builder, ti: ^Type_Info) -> int { n, _ := write_type_writer(strings.to_writer(buf), ti) return n } -write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { +write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { defer if n_written != nil { n_written^ += n } @@ -656,15 +654,16 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - } io.write_string(w, "struct ", &n) or_return - if info.is_packed { io.write_string(w, "#packed ", &n) or_return } - if info.is_raw_union { io.write_string(w, "#raw_union ", &n) or_return } - if info.custom_align { + if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return } + if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return } + if .no_copy in info.flags { io.write_string(w, "#no_copy ", &n) or_return } + if .align in info.flags { io.write_string(w, "#align(", &n) or_return io.write_i64(w, i64(ti.align), 10, &n) or_return io.write_string(w, ") ", &n) or_return } io.write_byte(w, '{', &n) or_return - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { io.write_string(w, ", ", &n) or_return } io.write_string(w, name, &n) or_return io.write_string(w, ": ", &n) or_return @@ -722,7 +721,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - io.write_string(w, "bit_field ", &n) or_return write_type(w, info.backing_type, &n) or_return io.write_string(w, " {", &n) or_return - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { io.write_string(w, ", ", &n) or_return } io.write_string(w, name, &n) or_return io.write_string(w, ": ", &n) or_return diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin index 3a32de0d6..a2cd2e4d3 100644 --- a/core/simd/x86/aes.odin +++ b/core/simd/x86/aes.odin @@ -2,33 +2,33 @@ package simd_x86 @(require_results, enable_target_feature = "aes") -_mm_aesdec :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesdec_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesdec(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesdeclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesdeclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesdeclast(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesenc :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesenc_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesenc(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesenclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesenclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesenclast(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesimc :: #force_inline proc "c" (a: __m128i) -> __m128i { +_mm_aesimc_si128 :: #force_inline proc "c" (a: __m128i) -> __m128i { return aesimc(a) } @(require_results, enable_target_feature = "aes") -_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { - return aeskeygenassist(a, u8(IMM8)) +_mm_aeskeygenassist_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { + return aeskeygenassist(a, IMM8) } @@ -45,5 +45,5 @@ foreign _ { @(link_name = "llvm.x86.aesni.aesimc") aesimc :: proc(a: __m128i) -> __m128i --- @(link_name = "llvm.x86.aesni.aeskeygenassist") - aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i --- + aeskeygenassist :: proc(a: __m128i, #const imm8: u8) -> __m128i --- } diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index fc2fec300..426359031 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -35,7 +35,7 @@ _mm_add_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add(transmute(i64x2)a, transmute(i64x2)b) + return simd.add(a, b) } @(require_results, enable_target_feature="sse2") _mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -118,7 +118,7 @@ _mm_sub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_sub_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub(transmute(i64x2)a, transmute(i64x2)b) + return simd.sub(a, b) } @(require_results, enable_target_feature="sse2") _mm_subs_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -144,19 +144,26 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 & 0xff + // This needs to emit behavior identical to PSLLDQ which is as follows: + // + // TEMP := COUNT + // IF (TEMP > 15) THEN TEMP := 16; FI + // DEST := DEST << (TEMP * 8) + // DEST[MAXVL-1:128] (Unmodified) + return transmute(__m128i)simd.shuffle( - transmute(i8x16)a, i8x16(0), - 0 when shift > 15 else (16 - shift + 0), - 1 when shift > 15 else (16 - shift + 1), - 2 when shift > 15 else (16 - shift + 2), - 3 when shift > 15 else (16 - shift + 3), - 4 when shift > 15 else (16 - shift + 4), - 5 when shift > 15 else (16 - shift + 5), - 6 when shift > 15 else (16 - shift + 6), - 7 when shift > 15 else (16 - shift + 7), - 8 when shift > 15 else (16 - shift + 8), - 9 when shift > 15 else (16 - shift + 9), + transmute(i8x16)a, + 0 when shift > 15 else (16 - shift + 0), + 1 when shift > 15 else (16 - shift + 1), + 2 when shift > 15 else (16 - shift + 2), + 3 when shift > 15 else (16 - shift + 3), + 4 when shift > 15 else (16 - shift + 4), + 5 when shift > 15 else (16 - shift + 5), + 6 when shift > 15 else (16 - shift + 6), + 7 when shift > 15 else (16 - shift + 7), + 8 when shift > 15 else (16 - shift + 8), + 9 when shift > 15 else (16 - shift + 9), 10 when shift > 15 else (16 - shift + 10), 11 when shift > 15 else (16 - shift + 11), 12 when shift > 15 else (16 - shift + 12), @@ -229,7 +236,7 @@ _mm_slli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_sll_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { - return transmute(__m128i)psllq(transmute(i64x2)a, transmute(i64x2)count) + return psllq(a, count) } @(require_results, enable_target_feature="sse2") _mm_srai_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { @@ -275,7 +282,7 @@ _mm_srli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_srl_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { - return transmute(__m128i)psrlq(transmute(i64x2)a, transmute(i64x2)count) + return psrlq(a, count) } @@ -363,7 +370,7 @@ _mm_cvtsi128_si32 :: #force_inline proc "c" (a: __m128i) -> i32 { @(require_results, enable_target_feature="sse2") _mm_set_epi64x :: #force_inline proc "c" (e1, e0: i64) -> __m128i { - return transmute(__m128i)i64x2{e0, e1} + return i64x2{e0, e1} } @(require_results, enable_target_feature="sse2") _mm_set_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { @@ -435,7 +442,7 @@ _mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { } @(enable_target_feature="sse2") _mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { - storeudq(mem_addr, a) + intrinsics.unaligned_store(mem_addr, a) } @(enable_target_feature="sse2") _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { @@ -453,7 +460,7 @@ _mm_stream_si32 :: #force_inline proc "c" (mem_addr: ^i32, a: i32) { @(require_results, enable_target_feature="sse2") _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { zero := _mm_setzero_si128() - return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)zero, 0, 2) + return simd.shuffle(a, zero, 0, 2) } @@ -545,7 +552,7 @@ _mm_unpackhi_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_unpackhi_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 1, 3) + return simd.shuffle(a, b, 1, 3) } @(require_results, enable_target_feature="sse2") _mm_unpacklo_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -565,7 +572,7 @@ _mm_unpacklo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 0, 2) + return simd.shuffle(a, b, 0, 2) } @@ -1023,7 +1030,7 @@ when ODIN_ARCH == .amd64 { } @(require_results, enable_target_feature="sse2") _mm_cvtsi128_si64 :: #force_inline proc "c" (a: __m128i) -> i64 { - return simd.extract(transmute(i64x2)a, 0) + return simd.extract(a, 0) } @(require_results, enable_target_feature="sse2") _mm_cvtsi128_si64x :: #force_inline proc "c" (a: __m128i) -> i64 { @@ -1178,8 +1185,6 @@ foreign _ { cvttsd2si :: proc(a: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.cvttps2dq") cvttps2dq :: proc(a: __m128) -> i32x4 --- - @(link_name="llvm.x86.sse2.storeu.dq") - storeudq :: proc(mem_addr: rawptr, a: __m128i) --- @(link_name="llvm.x86.sse2.storeu.pd") storeupd :: proc(mem_addr: rawptr, a: __m128d) --- diff --git a/core/simd/x86/sse41.odin b/core/simd/x86/sse41.odin index 0b9c5986f..c2c1abc2d 100644 --- a/core/simd/x86/sse41.odin +++ b/core/simd/x86/sse41.odin @@ -106,7 +106,7 @@ _mm_packus_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse4.1") _mm_cmpeq_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.lanes_eq(transmute(i64x2)a, transmute(i64x2)b) + return transmute(__m128i)simd.lanes_eq(a, b) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepi8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -124,7 +124,7 @@ _mm_cvtepi8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepi8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i8x16)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepi16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -136,13 +136,13 @@ _mm_cvtepi16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepi16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i16x8)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepi32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i32x4)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepu8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -160,7 +160,7 @@ _mm_cvtepu8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepu8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u8x16)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepu16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { @@ -172,13 +172,13 @@ _mm_cvtepu16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_cvtepu16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u16x8)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_cvtepu32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u32x4)a y := simd.shuffle(x, x, 0, 1) - return transmute(__m128i)i64x2(y) + return i64x2(y) } @(require_results, enable_target_feature="sse4.1") _mm_dp_pd :: #force_inline proc "c" (a, b: __m128d, $IMM8: u8) -> __m128d { @@ -242,7 +242,7 @@ _mm_minpos_epu16 :: #force_inline proc "c" (a: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse4.1") _mm_mul_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)pmuldq(transmute(i32x4)a, transmute(i32x4)b) + return pmuldq(transmute(i32x4)a, transmute(i32x4)b) } @(require_results, enable_target_feature="sse4.1") _mm_mullo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -254,15 +254,15 @@ _mm_mpsadbw_epu8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i } @(require_results, enable_target_feature="sse4.1") _mm_testz_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { - return ptestz(transmute(i64x2)a, transmute(i64x2)mask) + return ptestz(a, mask) } @(require_results, enable_target_feature="sse4.1") _mm_testc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { - return ptestc(transmute(i64x2)a, transmute(i64x2)mask) + return ptestc(a, mask) } @(require_results, enable_target_feature="sse4.1") _mm_testnzc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { - return ptestnzc(transmute(i64x2)a, transmute(i64x2)mask) + return ptestnzc(a, mask) } @(require_results, enable_target_feature="sse4.1") _mm_test_all_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { diff --git a/core/simd/x86/sse42.odin b/core/simd/x86/sse42.odin index 621346342..7a674176b 100644 --- a/core/simd/x86/sse42.odin +++ b/core/simd/x86/sse42.odin @@ -94,7 +94,7 @@ _mm_crc32_u32 :: #force_inline proc "c" (crc: u32, v: u32) -> u32 { } @(require_results, enable_target_feature="sse4.2") _mm_cmpgt_epi64 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { - return transmute(__m128i)simd.lanes_gt(transmute(i64x2)a, transmute(i64x2)b) + return transmute(__m128i)simd.lanes_gt(a, b) } when ODIN_ARCH == .amd64 { diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 902f1cdc5..dce9f834a 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -932,6 +932,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { nd := 0 nd_mant := 0 decimal_point := 0 + trailing_zeroes_nd := -1 loop: for ; i < len(s); i += 1 { switch c := s[i]; true { case c == '_': @@ -947,9 +948,16 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { case '0' <= c && c <= '9': saw_digits = true - if c == '0' && nd == 0 { - decimal_point -= 1 - continue loop + if c == '0' { + if nd == 0 { + decimal_point -= 1 + continue loop + } + if trailing_zeroes_nd == -1 { + trailing_zeroes_nd = nd + } + } else { + trailing_zeroes_nd = -1 } nd += 1 if nd_mant < MAX_MANT_DIGITS { @@ -981,6 +989,14 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { if !saw_dot { decimal_point = nd } + if trailing_zeroes_nd > 0 { + trailing_zeroes_nd = nd_mant - trailing_zeroes_nd + } + for /**/; trailing_zeroes_nd > 0; trailing_zeroes_nd -= 1 { + mantissa /= base + nd_mant -= 1 + nd -= 1 + } if base == 16 { decimal_point *= 4 nd_mant *= 4 diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 101ba72a9..e9b50bab0 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -531,6 +531,9 @@ Output: has_prefix :: proc(s, prefix: string) -> (result: bool) { return len(s) >= len(prefix) && s[0:len(prefix)] == prefix } + +starts_with :: has_prefix + /* Determines if a string `s` ends with a given `suffix` @@ -562,6 +565,9 @@ Output: has_suffix :: proc(s, suffix: string) -> (result: bool) { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix } + +ends_with :: has_suffix + /* Joins a slice of strings `a` with a `sep` string @@ -1001,11 +1007,6 @@ Returns: */ @private _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string, ok: bool) { - // stop once the string is empty or nil - if s == nil || len(s^) == 0 { - return - } - if sep == "" { res = s[:] ok = true @@ -2414,9 +2415,6 @@ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta } // Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) { - if state == nil { - return false - } cutset := (^string)(state)^ for c in cutset { if r == c { @@ -2714,7 +2712,7 @@ Output: */ split_multi_iterate :: proc(it: ^string, substrs: []string) -> (res: string, ok: bool) #no_bounds_check { - if it == nil || len(it) == 0 || len(substrs) <= 0 { + if len(it) == 0 || len(substrs) <= 0 { return } diff --git a/core/sync/atomic.odin b/core/sync/atomic.odin index 65d063f15..7e514a6b4 100644 --- a/core/sync/atomic.odin +++ b/core/sync/atomic.odin @@ -2,44 +2,452 @@ package sync import "base:intrinsics" +/* +This procedure may lower CPU consumption or yield to a hyperthreaded twin +processor. It's exact function is architecture specific, but the intent is to +say that you're not doing much on a CPU. +*/ cpu_relax :: intrinsics.cpu_relax /* -Atomic_Memory_Order :: enum { - Relaxed = 0, // Unordered - Consume = 1, // Monotonic - Acquire = 2, - Release = 3, - Acq_Rel = 4, - Seq_Cst = 5, -} +Describes memory ordering for an atomic operation. + +Modern CPU's contain multiple cores and caches specific to those cores. When a +core performs a write to memory, the value is written to cache first. The issue +is that a core doesn't typically see what's inside the caches of other cores. +In order to make operations consistent CPU's implement mechanisms that +synchronize memory operations across cores by asking other cores or by +pushing data about writes to other cores. + +Due to how these algorithms are implemented, the stores and loads performed by +one core may seem to happen in a different order to another core. It also may +happen that a core reorders stores and loads (independent of how compiler put +them into the machine code). This can cause issues when trying to synchronize +multiple memory locations between two cores. Which is why CPU's allow for +stronger memory ordering guarantees if certain instructions or instruction +variants are used. + +In Odin there are 5 different memory ordering guaranties that can be provided +to an atomic operation: + +- `Relaxed`: The memory access (load or store) is unordered with respect to + other memory accesses. This can be used to implement an atomic counter. + Multiple threads access a single variable, but it doesn't matter when + exactly it gets incremented, because it will become eventually consistent. +- `Consume`: No loads or stores dependent on a memory location can be + reordered before a load with consume memory order. If other threads released + the same memory, it becomes visible. +- `Acquire`: No loads or stores on a memory location can be reordered before a + load of that memory location with acquire memory ordering. If other threads + release the same memory, it becomes visible. +- `Release`: No loads or stores on a memory location can be reordered after a + store of that memory location with release memory ordering. All threads that + acquire the same memory location will see all writes done by the current + thread. +- `Acq_Rel`: Acquire-release memory ordering: combines acquire and release + memory orderings in the same operation. +- `Seq_Cst`: Sequential consistency. The strongest memory ordering. A load will + always be an acquire operation, a store will always be a release operation, + and in addition to that all threads observe the same order of writes. + +Non-explicit atomics will always be sequentially consistent. + + Atomic_Memory_Order :: enum { + Relaxed = 0, // Unordered + Consume = 1, // Monotonic + Acquire = 2, + Release = 3, + Acq_Rel = 4, + Seq_Cst = 5, + } + +**Note(i386, x64)**: x86 has a very strong memory model by default. It +guarantees that all writes are ordered, stores and loads aren't reordered. In +a sense, all operations are at least acquire and release operations. If `lock` +prefix is used, all operations are sequentially consistent. If you use explicit +atomics, make sure you have the correct atomic memory order, because bugs likely +will not show up in x86, but may show up on e.g. arm. More on x86 memory +ordering can be found +[[here; https://www.cs.cmu.edu/~410-f10/doc/Intel_Reordering_318147.pdf]] */ Atomic_Memory_Order :: intrinsics.Atomic_Memory_Order +/* +Establish memory ordering. -atomic_thread_fence :: intrinsics.atomic_thread_fence -atomic_signal_fence :: intrinsics.atomic_signal_fence -atomic_store :: intrinsics.atomic_store -atomic_store_explicit :: intrinsics.atomic_store_explicit -atomic_load :: intrinsics.atomic_load -atomic_load_explicit :: intrinsics.atomic_load_explicit -atomic_add :: intrinsics.atomic_add -atomic_add_explicit :: intrinsics.atomic_add_explicit -atomic_sub :: intrinsics.atomic_sub -atomic_sub_explicit :: intrinsics.atomic_sub_explicit -atomic_and :: intrinsics.atomic_and -atomic_and_explicit :: intrinsics.atomic_and_explicit -atomic_nand :: intrinsics.atomic_nand -atomic_nand_explicit :: intrinsics.atomic_nand_explicit -atomic_or :: intrinsics.atomic_or -atomic_or_explicit :: intrinsics.atomic_or_explicit -atomic_xor :: intrinsics.atomic_xor -atomic_xor_explicit :: intrinsics.atomic_xor_explicit -atomic_exchange :: intrinsics.atomic_exchange -atomic_exchange_explicit :: intrinsics.atomic_exchange_explicit +This procedure establishes memory ordering, without an associated atomic +operation. +*/ +atomic_thread_fence :: intrinsics.atomic_thread_fence -// Returns value and optional ok boolean -atomic_compare_exchange_strong :: intrinsics.atomic_compare_exchange_strong +/* +Establish memory ordering between a current thread and a signal handler. + +This procedure establishes memory ordering between a thread and a signal +handler, that run on the same thread, without an associated atomic operation. +This procedure is equivalent to `atomic_thread_fence`, except it doesn't +issue any CPU instructions for memory ordering. +*/ +atomic_signal_fence :: intrinsics.atomic_signal_fence + +/* +Atomically store a value into memory. + +This procedure stores a value to a memory location in such a way that no other +thread is able to see partial reads. This operation is sequentially-consistent. +*/ +atomic_store :: intrinsics.atomic_store + +/* +Atomically store a value into memory with explicit memory ordering. + +This procedure stores a value to a memory location in such a way that no other +thread is able to see partial reads. The memory ordering of this operation is +as specified by the `order` parameter. +*/ +atomic_store_explicit :: intrinsics.atomic_store_explicit + +/* +Atomically load a value from memory. + +This procedure loads a value from a memory location in such a way that the +received value is not a partial read. The memory ordering of this operation is +sequentially-consistent. +*/ +atomic_load :: intrinsics.atomic_load + +/* +Atomically load a value from memory with explicit memory ordering. + +This procedure loads a value from a memory location in such a way that the +received value is not a partial read. The memory ordering of this operation +is as specified by the `order` parameter. +*/ +atomic_load_explicit :: intrinsics.atomic_load_explicit + +/* +Atomically add a value to the value stored in memory. + +This procedure loads a value from memory, adds the specified value to it, and +stores it back as an atomic operation. This operation is an atomic equivalent +of the following: + + dst^ += val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_add :: intrinsics.atomic_add + +/* +Atomically add a value to the value stored in memory. + +This procedure loads a value from memory, adds the specified value to it, and +stores it back as an atomic operation. This operation is an atomic equivalent +of the following: + + dst^ += val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_add_explicit :: intrinsics.atomic_add_explicit + +/* +Atomically subtract a value from the value stored in memory. + +This procedure loads a value from memory, subtracts the specified value from it, +and stores the result back as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ -= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_sub :: intrinsics.atomic_sub + +/* +Atomically subtract a value from the value stored in memory. + +This procedure loads a value from memory, subtracts the specified value from it, +and stores the result back as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ -= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_sub_explicit :: intrinsics.atomic_sub_explicit + +/* +Atomically replace the memory location with the result of AND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of AND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ &= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_and :: intrinsics.atomic_and + +/* +Atomically replace the memory location with the result of AND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of AND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ &= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_and_explicit :: intrinsics.atomic_and_explicit + +/* +Atomically replace the memory location with the result of NAND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of NAND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ = ~(dst^ & val) + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_nand :: intrinsics.atomic_nand + +/* +Atomically replace the memory location with the result of NAND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of NAND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ = ~(dst^ & val) + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_nand_explicit :: intrinsics.atomic_nand_explicit + +/* +Atomically replace the memory location with the result of OR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of OR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ |= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_or :: intrinsics.atomic_or + +/* +Atomically replace the memory location with the result of OR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of OR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ |= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_or_explicit :: intrinsics.atomic_or_explicit + +/* +Atomically replace the memory location with the result of XOR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of XOR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ ~= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_xor :: intrinsics.atomic_xor + +/* +Atomically replace the memory location with the result of XOR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of XOR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ ~= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_xor_explicit :: intrinsics.atomic_xor_explicit + +/* +Atomically exchange the value in a memory location, with the specified value. + +This procedure loads a value from the specified memory location, and stores the +specified value into that memory location. Then the loaded value is returned, +all done in a single atomic operation. This operation is an atomic equivalent +of the following: + + tmp := dst^ + dst^ = val + return tmp + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_exchange :: intrinsics.atomic_exchange + +/* +Atomically exchange the value in a memory location, with the specified value. + +This procedure loads a value from the specified memory location, and stores the +specified value into that memory location. Then the loaded value is returned, +all done in a single atomic operation. This operation is an atomic equivalent +of the following: + + tmp := dst^ + dst^ = val + return tmp + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_exchange_explicit :: intrinsics.atomic_exchange_explicit + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The strong version of compare exchange always returns true, when the returned +old value stored in location pointed to by `dst` and the `old` parameter are +equal. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for both of +of these operations is sequentially-consistent. +*/ +atomic_compare_exchange_strong :: intrinsics.atomic_compare_exchange_strong + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The strong version of compare exchange always returns true, when the returned +old value stored in location pointed to by `dst` and the `old` parameter are +equal. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for these +operations is as specified by `success` and `failure` parameters respectively. +*/ atomic_compare_exchange_strong_explicit :: intrinsics.atomic_compare_exchange_strong_explicit -atomic_compare_exchange_weak :: intrinsics.atomic_compare_exchange_weak -atomic_compare_exchange_weak_explicit :: intrinsics.atomic_compare_exchange_weak_explicit \ No newline at end of file + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + // may return false here + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The weak version of compare exchange may return false, even if `dst^ == old`. +On some platforms running weak compare exchange in a loop is faster than a +strong version. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for both +of these operations is sequentially-consistent. +*/ +atomic_compare_exchange_weak :: intrinsics.atomic_compare_exchange_weak + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + // may return false here + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The weak version of compare exchange may return false, even if `dst^ == old`. +On some platforms running weak compare exchange in a loop is faster than a +strong version. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for these +operations is as specified by the `success` and `failure` parameters +respectively. +*/ +atomic_compare_exchange_weak_explicit :: intrinsics.atomic_compare_exchange_weak_explicit \ No newline at end of file diff --git a/core/sync/doc.odin b/core/sync/doc.odin new file mode 100644 index 000000000..9876c46fb --- /dev/null +++ b/core/sync/doc.odin @@ -0,0 +1,21 @@ +/* +Synchronization primitives + +This package implements various synchronization primitives that can be used to +synchronize threads' access to shared memory. + +To limit or control the threads' access to shared memory typically the +following approaches are used: + +* Locks +* Lock-free + +When using locks, sections of the code that access shared memory (also known as +**critical sections**) are guarded by locks, allowing limited access to threads +and blocking the execution of any other threads. + +In lock-free programming the data itself is organized in such a way that threads +don't intervene much. It can be done via segmenting the data between threads, +and/or by using atomic operations. +*/ +package sync \ No newline at end of file diff --git a/core/sync/extended.odin b/core/sync/extended.odin index 781ed816e..b446fefa0 100644 --- a/core/sync/extended.odin +++ b/core/sync/extended.odin @@ -4,15 +4,41 @@ import "core:time" import vg "core:sys/valgrind" _ :: vg -// A Wait_Group waits for a collection of threads to finish -// -// A Wait_Group must not be copied after first use +/* +Wait group. + +Wait group is a synchronization primitive used by the waiting thread to wait, +until a all working threads finish work. + +The waiting thread first sets the number of working threads it will expect to +wait for using `wait_group_add` call, and start waiting using `wait_group_wait` +call. When worker threads complete their work, each of them will call +`wait_group_done`, and after all working threads have called this procedure, +the waiting thread will resume execution. + +For the purpose of keeping track whether all working threads have finished their +work, the wait group keeps an internal atomic counter. Initially, the waiting +thread might set it to a certain non-zero amount. When each working thread +completes the work, the internal counter is atomically decremented until it +reaches zero. When it reaches zero, the waiting thread is unblocked. The counter +is not allowed to become negative. + +**Note**: Just like any synchronization primitives, a wait group cannot be +copied after first use. See documentation for `Mutex` or `Cond`. +*/ Wait_Group :: struct #no_copy { counter: int, mutex: Mutex, cond: Cond, } +/* +Increment an internal counter of a wait group. + +This procedure atomicaly increments a number to the specified wait group's +internal counter by a specified amount. This operation can be done on any +thread. +*/ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) { if delta == 0 { return @@ -32,10 +58,23 @@ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) { } } +/* +Signal work done by a thread in a wait group. + +This procedure decrements the internal counter of the specified wait group and +wakes up the waiting thread. Once the internal counter reaches zero, the waiting +thread resumes execution. +*/ wait_group_done :: proc "contextless" (wg: ^Wait_Group) { wait_group_add(wg, -1) } +/* +Wait for all worker threads in the wait group. + +This procedure blocks the execution of the current thread, until the specified +wait group's internal counter reaches zero. +*/ wait_group_wait :: proc "contextless" (wg: ^Wait_Group) { guard(&wg.mutex) @@ -47,6 +86,14 @@ wait_group_wait :: proc "contextless" (wg: ^Wait_Group) { } } +/* +Wait for all worker threads in the wait group, or until timeout is reached. + +This procedure blocks the execution of the current thread, until the specified +wait group's internal counter reaches zero, or until the timeout is reached. + +This procedure returns `false`, if the timeout was reached, `true` otherwise. +*/ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: time.Duration) -> bool { if duration <= 0 { return false @@ -64,41 +111,43 @@ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: t return true } - - /* -A barrier enabling multiple threads to synchronize the beginning of some computation +Barrier. -Example: - package example +A barrier is a synchronization primitive enabling multiple threads to +synchronize the beginning of some computation. - import "core:fmt" - import "core:sync" - import "core:thread" +When `barrier_wait` procedure is called by any thread, that thread will block +the execution, until all threads associated with the barrier reach the same +point of execution and also call `barrier_wait`. - barrier := &sync.Barrier{} +when barrier is initialized, a `thread_count` parameter is passed, signifying +the amount of participant threads of the barrier. The barrier also keeps track +of an internal atomic counter. When a thread calls `barrier_wait`, the internal +counter is incremented. When the internal counter reaches `thread_count`, it is +reset and all threads waiting on the barrier are unblocked. - main :: proc "contextless" () { - fmt.println("Start") +This type of synchronization primitive can be used to synchronize "staged" +workloads, where the workload is split into stages, and until all threads have +completed the previous threads, no thread is allowed to start work on the next +stage. In this case, after each stage, a `barrier_wait` shall be inserted in the +thread procedure. - THREAD_COUNT :: 4 - threads: [THREAD_COUNT]^thread.Thread +**Example**: - sync.barrier_init(barrier, THREAD_COUNT) - - for _, i in threads { - threads[i] = thread.create_and_start(proc(t: ^thread.Thread) { - // Same messages will be printed together but without any interleaving - fmt.println("Getting ready!") - sync.barrier_wait(barrier) - fmt.println("Off their marks they go!") - }) - } - - for t in threads { - thread.destroy(t) // join and free thread - } - fmt.println("Finished") + THREAD_COUNT :: 4 + threads: [THREAD_COUNT]^thread.Thread + sync.barrier_init(barrier, THREAD_COUNT) + for _, i in threads { + threads[i] = thread.create_and_start(proc(t: ^thread.Thread) { + // Same messages will be printed together but without any interleaving + fmt.println("Getting ready!") + sync.barrier_wait(barrier) + fmt.println("Off their marks they go!") + }) + } + for t in threads { + thread.destroy(t) } */ Barrier :: struct #no_copy { @@ -109,6 +158,13 @@ Barrier :: struct #no_copy { thread_count: int, } +/* +Initialize a barrier. + + +This procedure initializes the barrier for the specified amount of participant +threads. +*/ barrier_init :: proc "contextless" (b: ^Barrier, thread_count: int) { when ODIN_VALGRIND_SUPPORT { vg.helgrind_barrier_resize_pre(b, uint(thread_count)) @@ -118,8 +174,13 @@ barrier_init :: proc "contextless" (b: ^Barrier, thread_count: int) { b.thread_count = thread_count } -// Block the current thread until all threads have rendezvoused -// Barrier can be reused after all threads rendezvoused once, and can be used continuously +/* +Block the current thread until all threads have rendezvoused. + +This procedure blocks the execution of the current thread, until all threads +have reached the same point in the execution of the thread proc. Multiple calls +to `barrier_wait` are allowed within the thread procedure. +*/ barrier_wait :: proc "contextless" (b: ^Barrier) -> (is_leader: bool) { when ODIN_VALGRIND_SUPPORT { vg.helgrind_barrier_wait_pre(b) @@ -140,15 +201,31 @@ barrier_wait :: proc "contextless" (b: ^Barrier) -> (is_leader: bool) { return true } +/* +Auto-reset event. +Represents a thread synchronization primitive that, when signalled, releases one +single waiting thread and then resets automatically to a state where it can be +signalled again. + +When a thread calls `auto_reset_event_wait`, it's execution will be blocked, +until the event is signalled by another thread. The call to +`auto_reset_event_signal` wakes up exactly one thread waiting for the event. +*/ Auto_Reset_Event :: struct #no_copy { // status == 0: Event is reset and no threads are waiting - // status == 1: Event is signaled + // status == 1: Event is signalled // status == -N: Event is reset and N threads are waiting status: i32, sema: Sema, } +/* +Signal an auto-reset event. + +This procedure signals an auto-reset event, waking up exactly one waiting +thread. +*/ auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) { old_status := atomic_load_explicit(&e.status, .Relaxed) for { @@ -163,6 +240,12 @@ auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) { } } +/* +Wait on an auto-reset event. + +This procedure blocks the execution of the current thread, until the event is +signalled by another thread. +*/ auto_reset_event_wait :: proc "contextless" (e: ^Auto_Reset_Event) { old_status := atomic_sub_explicit(&e.status, 1, .Acquire) if old_status < 1 { @@ -170,13 +253,35 @@ auto_reset_event_wait :: proc "contextless" (e: ^Auto_Reset_Event) { } } +/* +Ticket lock. +A ticket lock is a mutual exclusion lock that uses "tickets" to control which +thread is allowed into a critical section. +This synchronization primitive works just like spinlock, except that it implements +a "fairness" guarantee, making sure that each thread gets a roughly equal amount +of entries into the critical section. + +This type of synchronization primitive is applicable for short critical sections +in low-contention systems, as it uses a spinlock under the hood. +*/ Ticket_Mutex :: struct #no_copy { ticket: uint, serving: uint, } +/* +Acquire a lock on a ticket mutex. + +This procedure acquires a lock on a ticket mutex. If the ticket mutex is held +by another thread, this procedure also blocks the execution until the lock +can be acquired. + +Once the lock is acquired, any thread calling `ticket_mutex_lock` will be +blocked from entering any critical sections associated with the same ticket +mutex, until the lock is released. +*/ ticket_mutex_lock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { ticket := atomic_add_explicit(&m.ticket, 1, .Relaxed) for ticket != atomic_load_explicit(&m.serving, .Acquire) { @@ -184,44 +289,147 @@ ticket_mutex_lock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { } } +/* +Release a lock on a ticket mutex. + +This procedure releases the lock on a ticket mutex. If any of the threads are +waiting to acquire the lock, exactly one of those threads is unblocked and +allowed into the critical section. +*/ ticket_mutex_unlock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { atomic_add_explicit(&m.serving, 1, .Relaxed) } + +/* +Guard the current scope with a lock on a ticket mutex. + +This procedure acquires a lock on a ticket mutex. The lock is automatically +released at the end of callee's scope. If the mutex was already locked, this +procedure also blocks until the lock can be acquired. + +When a lock has been acquired, all threads attempting to acquire a lock will be +blocked from entering any critical sections associated with the ticket mutex, +until the lock is released. + +This procedure always returns `true`. This makes it easy to define a critical +section by putting the function inside the `if` statement. + +**Example**: + + if ticket_mutex_guard(&m) { + ... + } +*/ @(deferred_in=ticket_mutex_unlock) ticket_mutex_guard :: proc "contextless" (m: ^Ticket_Mutex) -> bool { ticket_mutex_lock(m) return true } +/* +Benaphore. +A benaphore is a combination of an atomic variable and a semaphore that can +improve locking efficiency in a no-contention system. Acquiring a benaphore +lock doesn't call into an internal semaphore, if no other thread in a middle of +a critical section. + +Once a lock on a benaphore is acquired by a thread, no other thread is allowed +into any critical sections, associted with the same benaphore, until the lock +is released. +*/ Benaphore :: struct #no_copy { counter: i32, sema: Sema, } +/* +Acquire a lock on a benaphore. + +This procedure acquires a lock on the specified benaphore. If the lock on a +benaphore is already held, this procedure also blocks the execution of the +current thread, until the lock could be acquired. + +Once a lock is acquired, all threads attempting to take a lock will be blocked +from entering any critical sections associated with the same benaphore, until +until the lock is released. +*/ benaphore_lock :: proc "contextless" (b: ^Benaphore) { if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { sema_wait(&b.sema) } } +/* +Try to acquire a lock on a benaphore. + +This procedure tries to acquire a lock on the specified benaphore. If it was +already locked, then the returned value is `false`, otherwise the lock is +acquired and the procedure returns `true`. + +If the lock is acquired, all threads that attempt to acquire a lock will be +blocked from entering any critical sections associated with the same benaphore, +until the lock is released. +*/ benaphore_try_lock :: proc "contextless" (b: ^Benaphore) -> bool { v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 0, 1, .Acquire, .Acquire) return v == 0 } +/* +Release a lock on a benaphore. + +This procedure releases a lock on the specified benaphore. If any of the threads +are waiting on the lock, exactly one thread is allowed into a critical section +associated with the same banaphore. +*/ benaphore_unlock :: proc "contextless" (b: ^Benaphore) { if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { sema_post(&b.sema) } } +/* +Guard the current scope with a lock on a benaphore. + +This procedure acquires a lock on a benaphore. The lock is automatically +released at the end of callee's scope. If the benaphore was already locked, this +procedure also blocks until the lock can be acquired. + +When a lock has been acquired, all threads attempting to acquire a lock will be +blocked from entering any critical sections associated with the same benaphore, +until the lock is released. + +This procedure always returns `true`. This makes it easy to define a critical +section by putting the function inside the `if` statement. + +**Example**: + + if benaphore_guard(&m) { + ... + } +*/ @(deferred_in=benaphore_unlock) benaphore_guard :: proc "contextless" (m: ^Benaphore) -> bool { benaphore_lock(m) return true } +/* +Recursive benaphore. + +Recurisve benaphore is just like a plain benaphore, except it allows reentrancy +into the critical section. + +When a lock is acquired on a benaphore, all other threads attempting to +acquire a lock on the same benaphore will be blocked from any critical sections, +associated with the same benaphore. + +When a lock is acquired on a benaphore by a thread, that thread is allowed +to acquire another lock on the same benaphore. When a thread has acquired the +lock on a benaphore, the benaphore will stay locked until the thread releases +the lock as many times as it has been locked by the thread. +*/ Recursive_Benaphore :: struct #no_copy { counter: int, owner: int, @@ -229,6 +437,16 @@ Recursive_Benaphore :: struct #no_copy { sema: Sema, } +/* +Acquire a lock on a recursive benaphore. + +This procedure acquires a lock on a recursive benaphore. If the benaphore is +held by another thread, this function blocks until the lock can be acquired. + +Once a lock is acquired, all other threads attempting to acquire a lock will +be blocked from entering any critical sections associated with the same +recursive benaphore, until the lock is released. +*/ recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { @@ -241,6 +459,17 @@ recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) { b.recursion += 1 } +/* +Try to acquire a lock on a recursive benaphore. + +This procedure attempts to acquire a lock on recursive benaphore. If the +benaphore is already held by a different thread, this procedure returns `false`. +Otherwise the lock is acquired and the procedure returns `true`. + +If the lock is acquired, all other threads attempting to acquire a lock will +be blocked from entering any critical sections assciated with the same recursive +benaphore, until the lock is released. +*/ recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) -> bool { tid := current_thread_id() if b.owner == tid { @@ -256,6 +485,13 @@ recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) -> return true } +/* +Release a lock on a recursive benaphore. + +This procedure releases a lock on the specified recursive benaphore. It also +causes the critical sections associated with the same benaphore, to become open +for other threads for entering. +*/ recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() _assert(tid == b.owner, "tid != b.owner") @@ -272,24 +508,50 @@ recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) { // outside the lock } +/* +Guard the current scope with a recursive benaphore. + +This procedure acquires a lock on the specified recursive benaphores and +automatically releases it at the end of the callee's scope. If the recursive +benaphore was already held by a another thread, this procedure also blocks until +the lock can be acquired. + +When the lock is acquired all other threads attempting to take a lock will be +blocked from entering any critical sections associated with the same benaphore, +until the lock is released. + +This procedure always returns `true`, which makes it easy to define a critical +section by calling this procedure inside an `if` statement. + +**Example**: + + if recursive_benaphore_guard(&m) { + ... + } +*/ @(deferred_in=recursive_benaphore_unlock) recursive_benaphore_guard :: proc "contextless" (m: ^Recursive_Benaphore) -> bool { recursive_benaphore_lock(m) return true } +/* +Once action. - - -// Once is a data value that will perform exactly on action. -// -// A Once must not be copied after first use. +`Once` a synchronization primitive, that only allows a single entry into a +critical section from a single thread. +*/ Once :: struct #no_copy { m: Mutex, done: bool, } -// once_do calls the procedure fn if and only if once_do is being called for the first for this instance of Once. +/* +Call a function once. + +The `once_do` procedure group calls a specified function, if it wasn't already +called from the perspective of a specific `Once` struct. +*/ once_do :: proc{ once_do_without_data, once_do_without_data_contextless, @@ -297,7 +559,9 @@ once_do :: proc{ once_do_with_data_contextless, } -// once_do_without_data calls the procedure fn if and only if once_do_without_data is being called for the first for this instance of Once. +/* +Call a function with no data once. +*/ once_do_without_data :: proc(o: ^Once, fn: proc()) { @(cold) do_slow :: proc(o: ^Once, fn: proc()) { @@ -313,7 +577,9 @@ once_do_without_data :: proc(o: ^Once, fn: proc()) { } } -// once_do_without_data calls the procedure fn if and only if once_do_without_data is being called for the first for this instance of Once. +/* +Call a contextless function with no data once. +*/ once_do_without_data_contextless :: proc(o: ^Once, fn: proc "contextless" ()) { @(cold) do_slow :: proc(o: ^Once, fn: proc "contextless" ()) { @@ -329,7 +595,9 @@ once_do_without_data_contextless :: proc(o: ^Once, fn: proc "contextless" ()) { } } -// once_do_with_data calls the procedure fn if and only if once_do_with_data is being called for the first for this instance of Once. +/* +Call a function with data once. +*/ once_do_with_data :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) { @(cold) do_slow :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) { @@ -345,7 +613,9 @@ once_do_with_data :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) { } } -// once_do_with_data_contextless calls the procedure fn if and only if once_do_with_data_contextless is being called for the first for this instance of Once. +/* +Call a contextless function with data once. +*/ once_do_with_data_contextless :: proc "contextless" (o: ^Once, fn: proc "contextless" (data: rawptr), data: rawptr) { @(cold) do_slow :: proc "contextless" (o: ^Once, fn: proc "contextless" (data: rawptr), data: rawptr) { @@ -361,83 +631,112 @@ once_do_with_data_contextless :: proc "contextless" (o: ^Once, fn: proc "context } } +/* +A Parker is an associated token which is initially not present: - - - -// A Parker is an associated token which is initially not present: -// * The `park` procedure blocks the current thread unless or until the token -// is available, at which point the token is consumed. -// * The `park_with_timeout` procedures works the same as `park` but only -// blocks for the specified duration. -// * The `unpark` procedure automatically makes the token available if it -// was not already. +* The `park` procedure blocks the current thread unless or until the token + is available, at which point the token is consumed. +* The `park_with_timeout` procedures works the same as `park` but only + blocks for the specified duration. +* The `unpark` procedure automatically makes the token available if it + was not already. +*/ Parker :: struct #no_copy { state: Futex, } -// Blocks the current thread until the token is made available. -// -// Assumes this is only called by the thread that owns the Parker. +@(private="file") PARKER_EMPTY :: 0 +@(private="file") PARKER_NOTIFIED :: 1 +@(private="file") PARKER_PARKED :: max(u32) + +/* +Blocks until the token is available. + +This procedure blocks the execution of the current thread, until a token is +made available. + +**Note**: This procedure assumes this is only called by the thread that owns +the Parker. +*/ park :: proc "contextless" (p: ^Parker) { - EMPTY :: 0 - NOTIFIED :: 1 - PARKED :: max(u32) - if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED { + if atomic_sub_explicit(&p.state, 1, .Acquire) == PARKER_NOTIFIED { return } for { - futex_wait(&p.state, PARKED) - if _, ok := atomic_compare_exchange_strong_explicit(&p.state, NOTIFIED, EMPTY, .Acquire, .Acquire); ok { + futex_wait(&p.state, PARKER_PARKED) + if _, ok := atomic_compare_exchange_strong_explicit(&p.state, PARKER_NOTIFIED, PARKER_EMPTY, .Acquire, .Acquire); ok { return } } } -// Blocks the current thread until the token is made available, but only -// for a limited duration. -// -// Assumes this is only called by the thread that owns the Parker +/* +Blocks until the token is available with timeout. + +This procedure blocks the execution of the current thread until a token is made +available, or until the timeout has expired, whatever happens first. + +**Note**: This procedure assumes this is only called by the thread that owns +the Parker. +*/ park_with_timeout :: proc "contextless" (p: ^Parker, duration: time.Duration) { - EMPTY :: 0 - NOTIFIED :: 1 - PARKED :: max(u32) - if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED { + start_tick := time.tick_now() + remaining_duration := duration + if atomic_sub_explicit(&p.state, 1, .Acquire) == PARKER_NOTIFIED { return } - futex_wait_with_timeout(&p.state, PARKED, duration) - atomic_exchange_explicit(&p.state, EMPTY, .Acquire) + for { + if !futex_wait_with_timeout(&p.state, PARKER_PARKED, remaining_duration) { + return + } + old, ok := atomic_compare_exchange_weak_explicit((^u32)(&p.state), PARKER_PARKED, PARKER_EMPTY, .Acquire, .Relaxed) + if ok || old == PARKER_PARKED { + return + } + end_tick := time.tick_now() + remaining_duration -= time.tick_diff(start_tick, end_tick) + start_tick = end_tick + } } -// Automatically makes thee token available if it was not already. +/* +Make the token available. +*/ unpark :: proc "contextless" (p: ^Parker) { - EMPTY :: 0 - NOTIFIED :: 1 - PARKED :: max(Futex) - if atomic_exchange_explicit(&p.state, NOTIFIED, .Release) == PARKED { + if atomic_exchange_explicit((^u32)(&p.state), PARKER_NOTIFIED, .Release) == PARKER_PARKED { futex_signal(&p.state) } } +/* +One-shot event. +A one-shot event is an associated token which is initially not present: -// A One_Shot_Event is an associated token which is initially not present: -// * The `one_shot_event_wait` blocks the current thread until the event -// is made available -// * The `one_shot_event_signal` procedure automatically makes the token -// available if its was not already. +* The `one_shot_event_wait` blocks the current thread until the event + is made available +* The `one_shot_event_signal` procedure automatically makes the token + available if its was not already. +*/ One_Shot_Event :: struct #no_copy { state: Futex, } -// Blocks the current thread until the event is made available with `one_shot_event_signal`. +/* +Block until the event is made available. + +This procedure blocks the execution of the current thread, until the event is +made available. +*/ one_shot_event_wait :: proc "contextless" (e: ^One_Shot_Event) { for atomic_load_explicit(&e.state, .Acquire) == 0 { futex_wait(&e.state, 0) } } -// Releases any threads that are currently blocked by this event with `one_shot_event_wait`. +/* +Make event available. +*/ one_shot_event_signal :: proc "contextless" (e: ^One_Shot_Event) { atomic_store_explicit(&e.state, 1, .Release) futex_broadcast(&e.state) diff --git a/core/sync/futex_haiku.odin b/core/sync/futex_haiku.odin index b81743cad..6fe5894a0 100644 --- a/core/sync/futex_haiku.odin +++ b/core/sync/futex_haiku.odin @@ -2,7 +2,6 @@ package sync import "core:c" -import "base:runtime" import "core:sys/haiku" import "core:sys/unix" import "core:time" @@ -86,10 +85,10 @@ _futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) { waiter.prev.next = waiter.next waiter.next.prev = waiter.prev - unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil) + _ = unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil) // FIXME: Add error handling! - return + return } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) { @@ -133,10 +132,10 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration waiter.prev.next = waiter.next waiter.next.prev = waiter.prev - unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil) + unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil) - // FIXME: Add error handling! - return + // FIXME: Add error handling! + return } _futex_signal :: proc "contextless" (f: ^Futex) { diff --git a/core/sync/primitives.odin b/core/sync/primitives.odin index 8fa3dd232..a22824481 100644 --- a/core/sync/primitives.odin +++ b/core/sync/primitives.odin @@ -3,46 +3,108 @@ package sync import "base:runtime" import "core:time" +/* +Obtain the current thread ID. +*/ current_thread_id :: proc "contextless" () -> int { return _current_thread_id() } -// A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]] -// It can be used to prevent more than one thread from executing the same piece of code, -// and thus prevent access to same piece of memory by multiple threads, at the same time. -// -// A Mutex's zero value represents an initial, *unlocked* state. -// -// If another thread tries to take the lock while another thread holds it, it will pause -// until the lock is released. Code or memory that is "surrounded" by a mutex lock is said -// to be "guarded by a mutex". -// -// A Mutex must not be copied after first use (e.g., after locking it the first time). -// This is because, in order to coordinate with other threads, all threads must watch -// the same memory address to know when the lock has been released. Trying to use a -// copy of the lock at a different memory address will result in broken and unsafe -// behavior. For this reason, Mutexes are marked as `#no_copy`. +/* +Mutual exclusion lock. + +A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]] +It can be used to prevent more than one thread from entering the critical +section, and thus prevent access to same piece of memory by multiple threads, at +the same time. + +Mutex's zero-initializzed value represents an initial, *unlocked* state. + +If another thread tries to acquire the lock, while it's already held (typically +by another thread), the thread's execution will be blocked, until the lock is +released. Code or memory that is "surrounded" by a mutex lock and unlock +operations is said to be "guarded by a mutex". + +**Note**: A Mutex must not be copied after first use (e.g., after locking it the +first time). This is because, in order to coordinate with other threads, all +threads must watch the same memory address to know when the lock has been +released. Trying to use a copy of the lock at a different memory address will +result in broken and unsafe behavior. For this reason, Mutexes are marked as +`#no_copy`. + +**Note**: If the current thread attempts to lock a mutex, while it's already +holding another lock, that will cause a trivial case of deadlock. Do not use +`Mutex` in recursive functions. In case multiple locks by the same thread are +desired, use `Recursive_Mutex`. +*/ Mutex :: struct #no_copy { impl: _Mutex, } -// mutex_lock locks m +/* +Acquire a lock on a mutex. + +This procedure acquires a lock with the specified mutex. If the mutex has been +already locked by any thread, this procedure also blocks until the lock can be +acquired. + +Once the lock is acquired, all other threads that attempt to acquire a lock will +be blocked from entering any critical sections associated with the same mutex, +until the the lock is released. + +**Note**: If the mutex is already locked by the current thread, a call to this +procedure will block indefinately. Do not use this in recursive procedures. +*/ mutex_lock :: proc "contextless" (m: ^Mutex) { _mutex_lock(m) } -// mutex_unlock unlocks m +/* +Release a lock on a mutex. + +This procedure releases the lock associated with the specified mutex. If the +mutex was not locked, this operation is a no-op. + +When the current thread, that holds a lock to the mutex calls `mutex_unlock`, +this allows one other thread waiting on the mutex to enter any critical sections +associated with the mutex. If there are no threads waiting on the mutex, the +critical sections will remain open. +*/ mutex_unlock :: proc "contextless" (m: ^Mutex) { _mutex_unlock(m) } -// mutex_try_lock tries to lock m, will return true on success, and false on failure +/* +Try to acquire a lock on a mutex. + +This procedure tries to acquire a lock on the specified mutex. If it was already +locked, then the returned value is `false`, otherwise the lock is acquired and +the procedure returns `true`. + +If the lock is acquired, all threads that attempt to acquire a lock will be +blocked from entering any critical sections associated with the same mutex, +until the lock is released. +*/ mutex_try_lock :: proc "contextless" (m: ^Mutex) -> bool { return _mutex_try_lock(m) } /* -Example: +Guard the current scope with a lock on a mutex. + +This procedure acquires a mutex lock. The lock is automatically released +at the end of callee's scope. If the mutex was already locked, this procedure +also blocks until the lock can be acquired. + +When a lock has been acquired, all threads attempting to acquire a lock will be +blocked from entering any critical sections associated with the mutex, until +the lock is released. + +This procedure always returns `true`. This makes it easy to define a critical +section by putting the function inside the `if` statement. + +**Example**: + if mutex_guard(&m) { ... } @@ -53,47 +115,145 @@ mutex_guard :: proc "contextless" (m: ^Mutex) -> bool { return true } -// A RW_Mutex is a reader/writer mutual exclusion lock -// The lock can be held by any arbitrary number of readers or a single writer -// The zero value for a RW_Mutex is an unlocked mutex -// -// A RW_Mutex must not be copied after first use +/* +Read-write mutual exclusion lock. + +An `RW_Mutex` is a reader/writer mutual exclusion lock. The lock can be held by +any number of readers or a single writer. + +This type of synchronization primitive supports two kinds of lock operations: + +- Exclusive lock (write lock) +- Shared lock (read lock) + +When an exclusive lock is acquired by any thread, all other threads, attempting +to acquire either an exclusive or shared lock, will be blocked from entering the +critical sections associated with the read-write mutex, until the exclusive +owner of the lock releases the lock. + +When a shared lock is acquired by any thread, any other thread attempting to +acquire a shared lock will also be able to enter all the critical sections +associated with the read-write mutex. However threads attempting to acquire +an exclusive lock will be blocked from entering those critical sections, until +all shared locks are released. + +**Note**: A read-write mutex must not be copied after first use (e.g., after +acquiring a lock). This is because, in order to coordinate with other threads, +all threads must watch the same memory address to know when the lock has been +released. Trying to use a copy of the lock at a different memory address will +result in broken and unsafe behavior. For this reason, mutexes are marked as +`#no_copy`. + +**Note**: A read-write mutex is not recursive. Do not attempt to acquire an +exclusive lock more than once from the same thread, or an exclusive and shared +lock on the same thread. Taking a shared lock multiple times is acceptable. +*/ RW_Mutex :: struct #no_copy { impl: _RW_Mutex, } -// rw_mutex_lock locks rw for writing (with a single writer) -// If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available. +/* +Acquire an exclusive lock. + +This procedure acquires an exclusive lock on the specified read-write mutex. If +the lock is already held by any thread, this procedure also blocks until the +lock can be acquired. + +After a lock has been acquired, any thread attempting to acquire any lock +will be blocked from entering any critical sections associated with the same +read-write mutex, until the exclusive lock is released. +*/ rw_mutex_lock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_lock(rw) } -// rw_mutex_unlock unlocks rw for writing (with a single writer) +/* +Release an exclusive lock. + +This procedure releases an exclusive lock associated with the specified +read-write mutex. + +When the exclusive lock is released, all critical sections, associated with the +same read-write mutex, become open to other threads. +*/ rw_mutex_unlock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_unlock(rw) } -// rw_mutex_try_lock tries to lock rw for writing (with a single writer) +/* +Try to acquire an exclusive lock on a read-write mutex. + +This procedure tries to acquire an exclusive lock on the specified read-write +mutex. If the mutex was already locked, the procedure returns `false`. Otherwise +it acquires the exclusive lock and returns `true`. + +If the lock has been acquired, all threads attempting to acquire any lock +will be blocked from entering any critical sections associated with the same +read-write mutex, until the exclusive locked is released. +*/ rw_mutex_try_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool { return _rw_mutex_try_lock(rw) } -// rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers) +/* +Acquire a shared lock on a read-write mutex. + +This procedure acquires a shared lock on the specified read-write mutex. If the +mutex already has an exclusive lock held, this procedure also blocks until the +lock can be acquired. + +After the shared lock is obtained, all threads attempting to acquire an +exclusive lock will be blocked from entering any critical sections associated +with the same read-write mutex, until all shared locks associated with the +specified read-write mutex are released. +*/ rw_mutex_shared_lock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_shared_lock(rw) } -// rw_mutex_shared_unlock unlocks rw for reading (with arbitrary number of readers) +/* +Release the shared lock on a read-write mutex. + +This procedure releases shared lock on the specified read-write mutex. When all +shared locks are released, all critical sections associated with the same +read-write mutex become open to other threads. +*/ rw_mutex_shared_unlock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_shared_unlock(rw) } -// rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers) +/* +Try to acquire a shared lock on a read-write mutex. + +This procedure attempts to acquire a lock on the specified read-write mutex. If +the mutex already has an exclusive lock held, this procedure returns `false`. +Otherwise, it acquires the lock on the mutex and returns `true`. + +If the shared lock has been acquired, it causes all threads attempting to +acquire the exclusive lock to be blocked from entering any critical sections +associated with the same read-write mutex, until all shared locks are released. +*/ rw_mutex_try_shared_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool { return _rw_mutex_try_shared_lock(rw) } + /* -Example: +Guard the current scope with an exclusive lock on a read-write mutex. + +This procedure acquires an exclusive lock on the specified read-write mutex. +This procedure automatically releases the lock at the end of the callee's scope. +If the mutex was already locked by readers or a writer, this procedure blocks, +until a lock can be acquired. + +When an exclusive lock is acquired, all other threads attempting to acquire an +exclusive lock will be blocked from entering any critical sections associated +with the same read-write mutex, until the exclusive lock is released. + +This procedure always returns `true`, which makes it easy to define a critical +section by running this procedure inside an `if` statement. + +**Example**: + if rw_mutex_guard(&m) { ... } @@ -105,8 +265,23 @@ rw_mutex_guard :: proc "contextless" (m: ^RW_Mutex) -> bool { } /* -Example: - if rw_mutex_shared_guard(&m) { +Guard the current scope with a shared lock on a read-write mutex. + +This procedure acquires a shared lock on the specified read-write mutex. This +procedure automatically releases the lock at the end of the callee's scope. If +the mutex already has an associated exclusive lock, this procedure blocks, until +a lock can be acquired. + +When a shared lock is obtained, all other threads attempting to obtain an +exclusive lock will be blocked from any critical sections, associated with the +same read-write mutex, until all shared locks are released. + +This procedure always returns `true`, which makes it easy to define a critical +section by running this procedure inside an `if` statement. + +**Example**: + + if rw_mutex_guard(&m) { ... } */ @@ -116,30 +291,91 @@ rw_mutex_shared_guard :: proc "contextless" (m: ^RW_Mutex) -> bool { return true } +/* +Recursive mutual exclusion lock. +Recurisve mutex is just like a plain mutex, except it allows reentrancy. In +order for a thread to release the mutex for other threads, the mutex needs to +be unlocked as many times, as it was locked. -// A Recursive_Mutex is a recursive mutual exclusion lock -// The zero value for a Recursive_Mutex is an unlocked mutex -// -// A Recursive_Mutex must not be copied after first use +When a lock is acquired on a recursive mutex, all other threads attempting to +acquire a lock on the same mutex will be blocked from any critical sections, +associated with the same recrusive mutex. + +When a lock is acquired on a recursive mutex by a thread, that thread is allowed +to acquire another lock on the same mutex. When a thread has acquired the lock +on a recursive mutex, the recursive mutex will stay locked until the thread +releases the lock as many times as it has been locked by the thread. + +**Note**: A recursive mutex must not be copied after first use (e.g., after +acquiring a lock). This is because, in order to coordinate with other threads, +all threads must watch the same memory address to know when the lock has been +released. Trying to use a copy of the lock at a different memory address will +result in broken and unsafe behavior. For this reason, mutexes are marked as +`#no_copy`. +*/ Recursive_Mutex :: struct #no_copy { impl: _Recursive_Mutex, } +/* +Acquire a lock on a recursive mutex. + +This procedure acquires a lock on the specified recursive mutex. If the lock is +acquired by a different thread, this procedure also blocks until the lock can be +acquired. + +When the lock is acquired, all other threads attempting to acquire a lock will +be blocked from entering any critical sections associated with the same mutex, +until the lock is released. +*/ recursive_mutex_lock :: proc "contextless" (m: ^Recursive_Mutex) { _recursive_mutex_lock(m) } +/* +Release a lock on a recursive mutex. + +This procedure releases a lock on the specified recursive mutex. It also causes +the critical sections associated with the same mutex, to become open for other +threads for entering. +*/ recursive_mutex_unlock :: proc "contextless" (m: ^Recursive_Mutex) { _recursive_mutex_unlock(m) } +/* +Try to acquire a lock on a recursive mutex. + +This procedure attempts to acquire a lock on the specified recursive mutex. If +the recursive mutex is locked by other threads, this procedure returns `false`. +Otherwise it locks the mutex and returns `true`. + +If the lock is acquired, all other threads attempting to obtain a lock will be +blocked from entering any critical sections associated with the same mutex, +until the lock is released. +*/ recursive_mutex_try_lock :: proc "contextless" (m: ^Recursive_Mutex) -> bool { return _recursive_mutex_try_lock(m) } /* -Example: +Guard the scope with a recursive mutex lock. + +This procedure acquires a lock on the specified recursive mutex and +automatically releases it at the end of the callee's scope. If the recursive +mutex was already held by a another thread, this procedure also blocks until the +lock can be acquired. + +When the lock is acquired all other threads attempting to take a lock will be +blocked from entering any critical sections associated with the same mutex, +until the lock is released. + +This procedure always returns `true`, which makes it easy to define a critical +section by calling this procedure inside an `if` statement. + +**Example**: + if recursive_mutex_guard(&m) { ... } @@ -150,19 +386,69 @@ recursive_mutex_guard :: proc "contextless" (m: ^Recursive_Mutex) -> bool { return true } +/* +A condition variable. -// Cond implements a condition variable, a rendezvous point for threads -// waiting for signalling the occurence of an event -// -// A Cond must not be copied after first use +`Cond` implements a condition variable, a rendezvous point for threads waiting +for signalling the occurence of an event. Condition variables are used on +conjuction with mutexes to provide a shared access to one or more shared +variable. + +A typical usage of condition variable is as follows. A thread that intends to +modify a shared variable shall: + +1. Acquire a lock on a mutex. +2. Modify the shared memory. +3. Release the lock. +3. Call `cond_signal` or `cond_broadcast`. + +A thread that intends to wait on a shared variable shall: + +1. Acquire a lock on a mutex. +2. Call `cond_wait` or `cond_wait_with_timeout` (will release the mutex). +3. Check the condition and keep waiting in a loop if not satisfied with result. + +**Note**: A condition variable must not be copied after first use (e.g., after +waiting on it the first time). This is because, in order to coordinate with +other threads, all threads must watch the same memory address to know when the +lock has been released. Trying to use a copy of the lock at a different memory +address will result in broken and unsafe behavior. For this reason, condition +variables are marked as `#no_copy`. +*/ Cond :: struct #no_copy { impl: _Cond, } +/* +Wait until the condition variable is signalled and release the associated mutex. + +This procedure blocks the current thread until the specified condition variable +is signalled, or until a spurious wakeup occurs. In addition, if the condition +has been signalled, this procedure releases the lock on the specified mutex. + +The mutex must be held by the calling thread, before calling the procedure. + +**Note**: This procedure can return on a spurious wake-up, even if the condition +variable was not signalled by a thread. +*/ cond_wait :: proc "contextless" (c: ^Cond, m: ^Mutex) { _cond_wait(c, m) } +/* +Wait until the condition variable is signalled or timeout is reached and release +the associated mutex. + +This procedure blocks the current thread until the specified condition variable +is signalled, a timeout is reached, or until a spurious wakeup occurs. In +addition, if the condition has been signalled, this procedure releases the +lock on the specified mutex. + +If the timeout was reached, this procedure returns `false`. Otherwise it returns +`true`. + +Before this procedure is called the mutex must be held by the calling thread. +*/ cond_wait_with_timeout :: proc "contextless" (c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { if duration <= 0 { return false @@ -170,51 +456,123 @@ cond_wait_with_timeout :: proc "contextless" (c: ^Cond, m: ^Mutex, duration: tim return _cond_wait_with_timeout(c, m, duration) } +/* +Wake up one thread that waits on a condition variable. + +This procedure causes exactly one thread waiting on the condition variable to +wake up. +*/ cond_signal :: proc "contextless" (c: ^Cond) { _cond_signal(c) } +/* +Wake up all threads that wait on a condition variable. + +This procedure causes all threads waiting on the condition variable to wake up. +*/ cond_broadcast :: proc "contextless" (c: ^Cond) { _cond_broadcast(c) } +/* +Semaphore. -// When waited upon, blocks until the internal count is greater than zero, then subtracts one. -// Posting to the semaphore increases the count by one, or the provided amount. -// -// A Sema must not be copied after first use +When waited upon, semaphore blocks until the internal count is greater than +zero, then decrements the internal counter by one. Posting to the semaphore +increases the count by one, or the provided amount. + +This type of synchronization primitives can be useful for implementing queues. +The internal counter of the semaphore can be thought of as the amount of items +in the queue. After a data has been pushed to the queue, the thread shall call +`sema_post()` procedure, increasing the counter. When a thread takes an item +from the queue to do the job, it shall call `sema_wait()`, waiting on the +semaphore counter to become non-zero and decreasing it, if necessary. + +**Note**: A semaphore must not be copied after first use (e.g., after posting +to it). This is because, in order to coordinate with other threads, all threads +must watch the same memory address to know when the lock has been released. +Trying to use a copy of the lock at a different memory address will result in +broken and unsafe behavior. For this reason, semaphores are marked as `#no_copy`. +*/ Sema :: struct #no_copy { impl: _Sema, } +/* +Increment the internal counter on a semaphore by the specified amount. + +This procedure increments the internal counter of the semaphore. If any of the +threads were waiting on the semaphore, up to `count` of threads will continue +the execution and enter the critical section. +*/ sema_post :: proc "contextless" (s: ^Sema, count := 1) { _sema_post(s, count) } +/* +Wait on a semaphore until the internal counter is non-zero. + +This procedure blocks the execution of the current thread, until the semaphore +counter is non-zero, and atomically decrements it by one, once the wait has +ended. +*/ sema_wait :: proc "contextless" (s: ^Sema) { _sema_wait(s) } +/* +Wait on a semaphore until the internal counter is non-zero or a timeout is reached. + +This procedure blocks the execution of the current thread, until the semaphore +counter is non-zero, and if so atomically decrements it by one, once the wait +has ended. If the specified timeout is reached, the function returns `false`, +otherwise it returns `true`. +*/ sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool { return _sema_wait_with_timeout(s, duration) } +/* +Fast userspace mutual exclusion lock. +Futex is a fast userspace mutual exclusion lock, that uses a pointer to a 32-bit +value as an identifier of the queue of waiting threads. The value pointed to +by that pointer can be used to store extra data. -// Futex is a fast userspace mutual exclusion lock, using a 32-bit memory address as a hint -// -// An Futex must not be copied after first use +**IMPORTANT**: A futex must not be copied after first use (e.g., after waiting +on it the first time, or signalling it). This is because, in order to coordinate +with other threads, all threads must watch the same memory address. Trying to +use a copy of the lock at a different memory address will result in broken and +unsafe behavior. +*/ Futex :: distinct u32 +/* +Sleep if the futex contains the expected value until it's signalled. + +If the value of the futex is `expected`, this procedure blocks the execution of +the current thread, until the futex is woken up, or until a spurious wakeup +occurs. +*/ futex_wait :: proc "contextless" (f: ^Futex, expected: u32) { if u32(atomic_load_explicit(f, .Acquire)) != expected { return } - - _assert(_futex_wait(f, expected), "futex_wait failure") + ok := _futex_wait(f, expected) + _assert(ok, "futex_wait failure") } -// returns true if the wait happened within the duration, false if it exceeded the time duration +/* +Sleep if the futex contains the expected value until it's signalled or the +timeout is reached. + +If the value of the futex is `expected`, this procedure blocks the execution of +the current thread, until the futex is signalled, a timeout is reached, or +until a spurious wakeup occurs. + +This procedure returns `false` if the timeout was reached, `true` otherwise. +*/ futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { if u32(atomic_load_explicit(f, .Acquire)) != expected { return true @@ -226,10 +584,16 @@ futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duratio return _futex_wait_with_timeout(f, expected, duration) } +/* +Wake up a single thread waiting on a futex. +*/ futex_signal :: proc "contextless" (f: ^Futex) { _futex_signal(f) } +/* +Wake up multiple threads waiting on a futex. +*/ futex_broadcast :: proc "contextless" (f: ^Futex) { _futex_broadcast(f) } diff --git a/core/sys/darwin/xnu_system_call_helpers.odin b/core/sys/darwin/xnu_system_call_helpers.odin index 753f7f058..7fa59bfe0 100644 --- a/core/sys/darwin/xnu_system_call_helpers.odin +++ b/core/sys/darwin/xnu_system_call_helpers.odin @@ -3,6 +3,10 @@ package darwin import "core:c" import "base:runtime" +// IMPORTANT NOTE: direct syscall usage is not allowed by Apple's review process of apps and should +// be entirely avoided in the builtin Odin collections, these are here for users if they don't +// care about the Apple review process. + // this package uses the sys prefix for the proc names to indicate that these aren't native syscalls but directly call such sys_write_string :: proc (fd: c.int, message: string) -> bool { return syscall_write(fd, raw_data(message), cast(u64)len(message)) diff --git a/core/sys/darwin/xnu_system_call_numbers.odin b/core/sys/darwin/xnu_system_call_numbers.odin index 39d902c79..00ab75a15 100644 --- a/core/sys/darwin/xnu_system_call_numbers.odin +++ b/core/sys/darwin/xnu_system_call_numbers.odin @@ -1,5 +1,9 @@ package darwin +// IMPORTANT NOTE: direct syscall usage is not allowed by Apple's review process of apps and should +// be entirely avoided in the builtin Odin collections, these are here for users if they don't +// care about the Apple review process. + unix_offset_syscall :: proc "contextless" (number: System_Call_Number) -> uintptr { return uintptr(number) + uintptr(0x2000000) } diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index 7100da4f1..d289ee7c1 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -3,6 +3,10 @@ package darwin import "core:c" import "base:intrinsics" +// IMPORTANT NOTE: direct syscall usage is not allowed by Apple's review process of apps and should +// be entirely avoided in the builtin Odin collections, these are here for users if they don't +// care about the Apple review process. + /* flock */ LOCK_SH :: 1 /* shared lock */ LOCK_EX :: 2 /* exclusive lock */ diff --git a/core/sys/haiku/os.odin b/core/sys/haiku/os.odin index 1e00145eb..883072c2d 100644 --- a/core/sys/haiku/os.odin +++ b/core/sys/haiku/os.odin @@ -399,7 +399,7 @@ cpu_topology_node_info :: struct { }, _package: struct { vendor: cpu_vendor, - cache_line_size: u32 + cache_line_size: u32, }, _core: struct { model: u32, diff --git a/core/sys/info/cpu_darwin_arm64.odin b/core/sys/info/cpu_darwin_arm64.odin index 336334bc0..ffa60d1cb 100644 --- a/core/sys/info/cpu_darwin_arm64.odin +++ b/core/sys/info/cpu_darwin_arm64.odin @@ -7,7 +7,7 @@ init_cpu_features :: proc "contextless" () { @(static) features: CPU_Features defer cpu_features = features - try_set :: proc "contextless" (name: string, feature: CPU_Feature) -> (ok: bool) { + try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) { support: b32 if ok = unix.sysctlbyname(name, &support); ok && support { features += { feature } diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 1e9e5bbbd..e10edf558 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -244,7 +244,7 @@ Mode_Bits :: enum { ISVTX = 9, // 0o0001000 ISGID = 10, // 0o0002000 ISUID = 11, // 0o0004000 - IFFIFO = 12, // 0o0010000 + IFIFO = 12, // 0o0010000 IFCHR = 13, // 0o0020000 IFDIR = 14, // 0o0040000 IFREG = 15, // 0o0100000 @@ -1815,3 +1815,11 @@ EPoll_Ctl_Opcode :: enum i32 { DEL = 2, MOD = 3, } + +/* + Bits for execveat(2) flags. +*/ +Execveat_Flags_Bits :: enum { + AT_SYMLINK_NOFOLLOW = 8, + AT_EMPTY_PATH = 12, +} diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 51f7db68f..f3e9f5ff9 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -39,11 +39,11 @@ PRIO_MIN :: -20 SIGRTMIN :: Signal(32) SIGRTMAX :: Signal(64) -S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFFIFO} +S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFIFO} S_IFSOCK :: Mode{.IFREG, .IFDIR} S_IFLNK :: Mode{.IFREG, .IFCHR} S_IFBLK :: Mode{.IFDIR, .IFCHR} -S_IFFIFO :: Mode{.IFFIFO} +S_IFIFO :: Mode{.IFIFO} S_IFCHR :: Mode{.IFCHR} S_IFDIR :: Mode{.IFDIR} S_IFREG :: Mode{.IFREG} @@ -51,7 +51,7 @@ S_IFREG :: Mode{.IFREG} /* Checks the Mode bits to see if the file is a named pipe (FIFO). */ -S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFFIFO == (m & S_IFMT))} +S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFIFO == (m & S_IFMT))} /* Check the Mode bits to see if the file is a character device. diff --git a/core/sys/linux/helpers.odin b/core/sys/linux/helpers.odin index 75fdd586e..f1abbbf61 100644 --- a/core/sys/linux/helpers.odin +++ b/core/sys/linux/helpers.odin @@ -12,7 +12,7 @@ import "base:intrinsics" @(private) syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int { - return cast(int) intrinsics.syscall(nr) + return int(intrinsics.syscall(nr)) } @(private) @@ -20,7 +20,7 @@ syscall1 :: #force_inline proc "contextless" (nr: uintptr, p1: $T) -> int where size_of(p1) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, cast(uintptr) p1) + return int(intrinsics.syscall(nr, uintptr(p1))) } @(private) @@ -29,8 +29,7 @@ where size_of(p1) <= size_of(uintptr), size_of(p2) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, cast(uintptr) p2) + return int(intrinsics.syscall(nr, uintptr(p1), uintptr(p2))) } @(private) @@ -40,10 +39,11 @@ where size_of(p2) <= size_of(uintptr), size_of(p3) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + )) } @(private) @@ -54,11 +54,12 @@ where size_of(p3) <= size_of(uintptr), size_of(p4) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + )) } @(private) @@ -70,12 +71,13 @@ where size_of(p4) <= size_of(uintptr), size_of(p5) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4, - cast(uintptr) p5) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + uintptr(p5), + )) } @(private) @@ -88,13 +90,14 @@ where size_of(p5) <= size_of(uintptr), size_of(p6) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4, - cast(uintptr) p5, - cast(uintptr) p6) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + uintptr(p5), + uintptr(p6), + )) } syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6} @@ -113,7 +116,7 @@ where default_value: T return default_value, Errno(-ret) } else { - return cast(T) transmute(U) ret, Errno(.NONE) + return T(transmute(U)ret), Errno(.NONE) } } @@ -123,7 +126,7 @@ errno_unwrap2 :: #force_inline proc "contextless" (ret: $P, $T: typeid) -> (T, E default_value: T return default_value, Errno(-ret) } else { - return cast(T) ret, Errno(.NONE) + return T(ret), Errno(.NONE) } } diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 450af9ca9..ec7357c48 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -749,17 +749,13 @@ getsockopt :: proc { getsockopt_base, } -// TODO(flysand): clone (probably not in this PR, maybe not ever) - /* Creates a copy of the running process. Available since Linux 1.0. */ fork :: proc "contextless" () -> (Pid, Errno) { when ODIN_ARCH == .arm64 { - // Note(flysand): this syscall is not documented, but the bottom 8 bits of flags - // are for exit signal - ret := syscall(SYS_clone, Signal.SIGCHLD) + ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0)) return errno_unwrap(ret, Pid) } else { ret := syscall(SYS_fork) @@ -789,7 +785,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { - ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) + ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0)) return Errno(-ret) } } @@ -1733,9 +1729,9 @@ getpgrp :: proc "contextless" () -> (Pid, Errno) { Create a session and set the process group ID. Available since Linux 2.0. */ -setsid :: proc "contextless" () -> (Errno) { +setsid :: proc "contextless" () -> (Pid, Errno) { ret := syscall(SYS_setsid) - return Errno(-ret) + return errno_unwrap(ret, Pid) } /* @@ -2818,7 +2814,7 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) { +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags = {}) -> (Errno) { ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) return Errno(-ret) } diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index e3fe67a9b..288edf879 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -688,7 +688,7 @@ Sock_Addr_In6 :: struct #packed { } /* - Struct representing Unix Domain Socket address + Struct representing Unix Domain Socket address */ Sock_Addr_Un :: struct #packed { sun_family: Address_Family, @@ -1303,3 +1303,8 @@ EPoll_Event :: struct #packed { events: EPoll_Event_Kind, data: EPoll_Data, } + +/* + Flags for execveat(2) syscall. +*/ +Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32] diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 6417961e5..14d3c113a 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -3,29 +3,60 @@ package unix import "base:intrinsics" -import "core:c" import "core:sys/darwin" _ :: darwin sysctl :: proc "contextless" (mib: []i32, val: ^$T) -> (ok: bool) { - result_size := c.size_t(size_of(T)) - res := darwin.syscall_sysctl( - raw_data(mib), len(mib), - val, &result_size, - nil, 0, - ) - return res == 0 + result_size := uint(size_of(T)) + when ODIN_NO_CRT { + res := darwin.syscall_sysctl( + raw_data(mib), len(mib), + val, &result_size, + nil, 0, + ) + return res == 0 + } else { + foreign { + @(link_name="sysctl") _sysctl :: proc( + name: [^]i32, namelen: u32, + oldp: rawptr, oldlenp: ^uint, + newp: rawptr, newlen: uint, + ) -> i32 --- + } + res := _sysctl( + raw_data(mib), u32(len(mib)), + val, &result_size, + nil, 0, + ) + return res == 0 + } } -sysctlbyname :: proc "contextless" (name: string, val: ^$T) -> (ok: bool) { - result_size := c.size_t(size_of(T)) - res := darwin.syscall_sysctlbyname( - name, - val, &result_size, - nil, 0, - ) - return res == 0 +sysctlbyname :: proc "contextless" (name: cstring, val: ^$T) -> (ok: bool) { + result_size := uint(size_of(T)) + when ODIN_NO_CRT { + res := darwin.syscall_sysctlbyname( + string(name), + val, &result_size, + nil, 0, + ) + return res == 0 + } else { + foreign { + @(link_name="sysctlbyname") _sysctlbyname :: proc( + name: cstring, + oldp: rawptr, oldlenp: ^uint, + newp: rawptr, newlen: uint, + ) -> i32 --- + } + res := _sysctlbyname( + name, + val, &result_size, + nil, 0, + ) + return res == 0 + } } // See sysctl.h for darwin for details diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin index e9ceb4667..6ae6c9151 100644 --- a/core/sys/wasm/wasi/wasi_api.odin +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -16,6 +16,7 @@ CLOCK_REALTIME :: clockid_t(2) CLOCK_THREAD_CPUTIME_ID :: clockid_t(3) errno_t :: enum u16 { + NONE = 0, // No error occurred. System call completed successfully. SUCCESS = 0, // Argument list too long. @@ -962,7 +963,7 @@ prestat_dir_t :: struct { } prestat_t :: struct { - tag: u8, + tag: preopentype_t, using u: struct { dir: prestat_dir_t, }, @@ -1158,7 +1159,7 @@ foreign wasi { /** * A buffer into which to write the preopened directory name. */ - path: string, + path: []byte, ) -> errno_t --- /** * Create a directory. diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index 163bf2a5e..1e34f1fd6 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -18,6 +18,14 @@ foreign advapi32 { OpenAsSelf: BOOL, TokenHandle: ^HANDLE) -> BOOL --- + GetTokenInformation :: proc ( + TokenHandle: HANDLE, + TokenInformationClass: TOKEN_INFORMATION_CLASS, + TokenInformation: LPVOID, + TokenInformationLength: DWORD, + ReturnLength: PDWORD, + ) -> BOOL --- + CryptAcquireContextW :: proc(hProv: ^HCRYPTPROV, szContainer, szProvider: wstring, dwProvType, dwFlags: DWORD) -> DWORD --- CryptGenRandom :: proc(hProv: HCRYPTPROV, dwLen: DWORD, buf: LPVOID) -> DWORD --- CryptReleaseContext :: proc(hProv: HCRYPTPROV, dwFlags: DWORD) -> DWORD --- @@ -44,7 +52,17 @@ foreign advapi32 { cbSid: ^DWORD, ReferencedDomainName: wstring, cchReferencedDomainName: ^DWORD, - peUse: ^SID_TYPE, + peUse: PSID_NAME_USE, + ) -> BOOL --- + + LookupAccountSidW :: proc ( + lpSystemName: LPCWSTR, + Sid: PSID, + Name: LPWSTR, + cchName: LPDWORD, + ReferencedDomainName: LPWSTR, + cchReferencedDomainName: LPDWORD, + peUse: PSID_NAME_USE, ) -> BOOL --- CreateProcessWithLogonW :: proc( @@ -134,6 +152,43 @@ foreign advapi32 { cbData: DWORD, ) -> LSTATUS --- + RegQueryInfoKeyW :: proc( + hKey: HKEY, + lpClass: LPWSTR, + lpcchClass: LPDWORD, + lpReserved: LPDWORD, + lpcSubKeys: LPDWORD, + lpcbMaxSubKeyLen: LPDWORD, + lpcbMaxClassLen: LPDWORD, + lpcValues: LPDWORD, + lpcbMaxValueNameLen: LPDWORD, + lpcbMaxValueLen: LPDWORD, + lpcbSecurityDescriptor: LPDWORD, + lpftLastWriteTime: ^FILETIME, + ) -> LSTATUS --- + + RegEnumKeyExW :: proc( + hKey: HKEY, + dwIndex: DWORD, + lpName: LPWSTR, + lpcchName: LPDWORD, + lpReserved: LPDWORD, + lpClass: LPWSTR, + lpcchClass: LPDWORD, + lpftLastWriteTime: ^FILETIME, + ) -> LSTATUS --- + + RegEnumValueW :: proc( + hKey: HKEY, + dwIndex: DWORD, + lpValueName: LPWSTR, + lpcchValueName: LPDWORD, + lpReserved: LPDWORD, + lpType: LPDWORD, + lpData: LPBYTE, + lpcbData: LPDWORD, + ) -> LSTATUS --- + GetFileSecurityW :: proc( lpFileName: LPCWSTR, RequestedInformation: SECURITY_INFORMATION, @@ -164,3 +219,156 @@ foreign advapi32 { AccessStatus: LPBOOL, ) -> BOOL --- } + +PTOKEN_INFORMATION_CLASS :: ^TOKEN_INFORMATION_CLASS +TOKEN_INFORMATION_CLASS :: enum i32 { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted, + TokenProcessTrustLevel, + TokenPrivateNameSpace, + TokenSingletonAttributes, + TokenBnoIsolation, + TokenChildProcessFlags, + TokenIsLessPrivilegedAppContainer, + TokenIsSandboxed, + TokenIsAppSilo, + TokenLoggingInformation, + MaxTokenInfoClass, +} + +PSID_NAME_USE :: ^SID_NAME_USE +SID_NAME_USE :: enum i32 { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer, + SidTypeLabel, + SidTypeLogonSession, +} + +PTOKEN_USER :: ^TOKEN_USER +TOKEN_USER :: struct { + User: SID_AND_ATTRIBUTES, +} + +PSID_AND_ATTRIBUTES :: ^SID_AND_ATTRIBUTES +SID_AND_ATTRIBUTES :: struct { + Sid: rawptr, + Attributes: ULONG, +} + +PTOKEN_TYPE :: ^TOKEN_TYPE +TOKEN_TYPE :: enum { + TokenPrimary = 1, + TokenImpersonation = 2, +} + +PTOKEN_STATISTICS :: ^TOKEN_STATISTICS +TOKEN_STATISTICS :: struct { + TokenId: LUID, + AuthenticationId: LUID, + ExpirationTime: LARGE_INTEGER, + TokenType: TOKEN_TYPE, + ImpersonationLevel: SECURITY_IMPERSONATION_LEVEL, + DynamicCharged: DWORD, + DynamicAvailable: DWORD, + GroupCount: DWORD, + PrivilegeCount: DWORD, + ModifiedId: LUID, +} + + +TOKEN_SOURCE_LENGTH :: 8 +PTOKEN_SOURCE :: ^TOKEN_SOURCE +TOKEN_SOURCE :: struct { + SourceName: [TOKEN_SOURCE_LENGTH]CHAR, + SourceIdentifier: LUID, +} + + +PTOKEN_PRIVILEGES :: ^TOKEN_PRIVILEGES +TOKEN_PRIVILEGES :: struct { + PrivilegeCount: DWORD, + Privileges: [0]LUID_AND_ATTRIBUTES, +} + +PTOKEN_PRIMARY_GROUP :: ^TOKEN_PRIMARY_GROUP +TOKEN_PRIMARY_GROUP :: struct { + PrimaryGroup: PSID, +} + +PTOKEN_OWNER :: ^TOKEN_OWNER +TOKEN_OWNER :: struct { + Owner: PSID, +} + +PTOKEN_GROUPS_AND_PRIVILEGES :: ^TOKEN_GROUPS_AND_PRIVILEGES +TOKEN_GROUPS_AND_PRIVILEGES :: struct { + SidCount: DWORD, + SidLength: DWORD, + Sids: PSID_AND_ATTRIBUTES, + RestrictedSidCount: DWORD, + RestrictedSidLength: DWORD, + RestrictedSids: PSID_AND_ATTRIBUTES, + PrivilegeCount: DWORD, + PrivilegeLength: DWORD, + Privileges: PLUID_AND_ATTRIBUTES, + AuthenticationId: LUID, +} + +PTOKEN_DEFAULT_DACL :: ^TOKEN_DEFAULT_DACL +TOKEN_DEFAULT_DACL :: struct { + DefaultDacl: PACL, +} + +PACL :: ^ACL +ACL :: struct { + AclRevision: BYTE, + Sbz1: BYTE, + AclSize: WORD, + AceCount: WORD, + Sbz2: WORD, +} diff --git a/core/sys/windows/codepage.odin b/core/sys/windows/codepage.odin new file mode 100644 index 000000000..90040f1ee --- /dev/null +++ b/core/sys/windows/codepage.odin @@ -0,0 +1,298 @@ +// +build windows +package sys_windows + +// https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers +CODEPAGE :: enum UINT { + // Default to ANSI code page + ACP = CP_ACP, + // Default to OEM code page + OEMCP = CP_OEMCP, + // Default to MAC code page + MACCP = CP_MACCP, + // Current thread's ANSI code page + THREAD_ACP = CP_THREAD_ACP, + // Symbol translations + SYMBOL = CP_SYMBOL, + + // IBM EBCDIC US-Canada + IBM037 = 037, + // OEM United States + IBM437 = 437, + // IBM EBCDIC International + IBM500 = 500, + // Arabic (ASMO 708) + ASMO_708 = 708, + // Arabic (Transparent ASMO); Arabic (DOS) + DOS_720 = 720, + // OEM Greek (formerly 437G); Greek (DOS) + IBM737 = 737, + // OEM Baltic; Baltic (DOS) + IBM775 = 775, + // OEM Multilingual Latin 1; Western European (DOS) + IBM850 = 850, + // OEM Latin 2; Central European (DOS) + IBM852 = 852, + // OEM Cyrillic (primarily Russian) + IBM855 = 855, + // OEM Turkish; Turkish (DOS) + IBM857 = 857, + // OEM Multilingual Latin 1 + Euro symbol + IBM00858 = 858, + // OEM Portuguese; Portuguese (DOS) + IBM860 = 860, + // OEM Icelandic; Icelandic (DOS) + IBM861 = 861, + // OEM Hebrew; Hebrew (DOS) + DOS_862 = 862, + // OEM French Canadian; French Canadian (DOS) + IBM863 = 863, + // OEM Arabic; Arabic (864) + IBM864 = 864, + // OEM Nordic; Nordic (DOS) + IBM865 = 865, + // OEM Russian; Cyrillic (DOS) + CP866 = 866, + // OEM Modern Greek; Greek, Modern (DOS) + IBM869 = 869, + // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2 + IBM870 = 870, + // Thai (Windows) + WINDOWS_874 = 874, + // IBM EBCDIC Greek Modern + CP875 = 875, + // ANSI/OEM Japanese; Japanese (Shift-JIS) + SHIFT_JIS = 932, + // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312) + GB2312 = 936, + // ANSI/OEM Korean (Unified Hangul Code) + KS_C_5601_1987 = 949, + // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5) + BIG5 = 950, + // IBM EBCDIC Turkish (Latin 5) + IBM1026 = 1026, + // IBM EBCDIC Latin 1/Open System + IBM01047 = 1047, + // IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro) + IBM01140 = 1140, + // IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro) + IBM01141 = 1141, + // IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro) + IBM01142 = 1142, + // IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro) + IBM01143 = 1143, + // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro) + IBM01144 = 1144, + // IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro) + IBM01145 = 1145, + // IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro) + IBM01146 = 1146, + // IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro) + IBM01147 = 1147, + // IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro) + IBM01148 = 1148, + // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro) + IBM01149 = 1149, + // Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications + UTF16 = 1200, + // Unicode UTF-16, big endian byte order; available only to managed applications + UNICODEFFFE = 1201, + // ANSI Central European; Central European (Windows) + WINDOWS_1250 = 1250, + // ANSI Cyrillic; Cyrillic (Windows) + WINDOWS_1251 = 1251, + // ANSI Latin 1; Western European (Windows) + WINDOWS_1252 = 1252, + // ANSI Greek; Greek (Windows) + WINDOWS_1253 = 1253, + // ANSI Turkish; Turkish (Windows) + WINDOWS_1254 = 1254, + // ANSI Hebrew; Hebrew (Windows) + WINDOWS_1255 = 1255, + // ANSI Arabic; Arabic (Windows) + WINDOWS_1256 = 1256, + // ANSI Baltic; Baltic (Windows) + WINDOWS_1257 = 1257, + // ANSI/OEM Vietnamese; Vietnamese (Windows) + WINDOWS_1258 = 1258, + // Korean (Johab) + JOHAB = 1361, + // MAC Roman; Western European (Mac) + MACINTOSH = 10000, + // Japanese (Mac) + X_MAC_JAPANESE = 10001, + // MAC Traditional Chinese (Big5); Chinese Traditional (Mac) + X_MAC_CHINESETRAD = 10002, + // Korean (Mac) + X_MAC_KOREAN = 10003, + // Arabic (Mac) + X_MAC_ARABIC = 10004, + // Hebrew (Mac) + X_MAC_HEBREW = 10005, + // Greek (Mac) + X_MAC_GREEK = 10006, + // Cyrillic (Mac) + X_MAC_CYRILLIC = 10007, + // MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac) + X_MAC_CHINESESIMP = 10008, + // Romanian (Mac) + X_MAC_ROMANIAN = 10010, + // Ukrainian (Mac) + X_MAC_UKRAINIAN = 10017, + // Thai (Mac) + X_MAC_THAI = 10021, + // MAC Latin 2; Central European (Mac) + X_MAC_CE = 10029, + // Icelandic (Mac) + X_MAC_ICELANDIC = 10079, + // Turkish (Mac) + X_MAC_TURKISH = 10081, + // Croatian (Mac) + X_MAC_CROATIAN = 10082, + // Unicode UTF-32, little endian byte order; available only to managed applications + UTF32 = 12000, + // Unicode UTF-32, big endian byte order; available only to managed applications + UTF32BE = 12001, + // CNS Taiwan; Chinese Traditional (CNS) + X_CHINESE_CNS = 20000, + // TCA Taiwan + X_CP20001 = 20001, + // Eten Taiwan; Chinese Traditional (Eten) + X_CHINESE_ETEN = 20002, + // IBM5550 Taiwan + X_CP20003 = 20003, + // TeleText Taiwan + X_CP20004 = 20004, + // Wang Taiwan + X_CP20005 = 20005, + // IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5) + X_IA5 = 20105, + // IA5 German (7-bit) + X_IA5_GERMAN = 20106, + // IA5 Swedish (7-bit) + X_IA5_SWEDISH = 20107, + // IA5 Norwegian (7-bit) + X_IA5_NORWEGIAN = 20108, + // US-ASCII (7-bit) + US_ASCII = 20127, + // T.61 + X_CP20261 = 20261, + // ISO 6937 Non-Spacing Accent + X_CP20269 = 20269, + // IBM EBCDIC Germany + IBM273 = 20273, + // IBM EBCDIC Denmark-Norway + IBM277 = 20277, + // IBM EBCDIC Finland-Sweden + IBM278 = 20278, + // IBM EBCDIC Italy + IBM280 = 20280, + // IBM EBCDIC Latin America-Spain + IBM284 = 20284, + // IBM EBCDIC United Kingdom + IBM285 = 20285, + // IBM EBCDIC Japanese Katakana Extended + IBM290 = 20290, + // IBM EBCDIC France + IBM297 = 20297, + // IBM EBCDIC Arabic + IBM420 = 20420, + // IBM EBCDIC Greek + IBM423 = 20423, + // IBM EBCDIC Hebrew + IBM424 = 20424, + // IBM EBCDIC Korean Extended + X_EBCDIC_KOREANEXTENDED = 20833, + // IBM EBCDIC Thai + IBM_THAI = 20838, + // Russian (KOI8-R); Cyrillic (KOI8-R) + KOI8_R = 20866, + // IBM EBCDIC Icelandic + IBM871 = 20871, + // IBM EBCDIC Cyrillic Russian + IBM880 = 20880, + // IBM EBCDIC Turkish + IBM905 = 20905, + // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol) + IBM00924 = 20924, + // Japanese (JIS 0208-1990 and 0212-1990) + EUC_JP = 20932, + // Simplified Chinese (GB2312); Chinese Simplified (GB2312-80) + X_CP20936 = 20936, + // Korean Wansung + X_CP20949 = 20949, + // IBM EBCDIC Cyrillic Serbian-Bulgarian + CP1025 = 21025, + // Ukrainian (KOI8-U); Cyrillic (KOI8-U) + KOI8_U = 21866, + // ISO 8859-1 Latin 1; Western European (ISO) + ISO_8859_1 = 28591, + // ISO 8859-2 Central European; Central European (ISO) + ISO_8859_2 = 28592, + // ISO 8859-3 Latin 3 + ISO_8859_3 = 28593, + // ISO 8859-4 Baltic + ISO_8859_4 = 28594, + // ISO 8859-5 Cyrillic + ISO_8859_5 = 28595, + // ISO 8859-6 Arabic + ISO_8859_6 = 28596, + // ISO 8859-7 Greek + ISO_8859_7 = 28597, + // ISO 8859-8 Hebrew; Hebrew (ISO-Visual) + ISO_8859_8 = 28598, + // ISO 8859-9 Turkish + ISO_8859_9 = 28599, + // ISO 8859-13 Estonian + ISO_8859_13 = 28603, + // ISO 8859-15 Latin 9 + ISO_8859_15 = 28605, + // Europa 3 + X_EUROPA = 29001, + // ISO 8859-8 Hebrew; Hebrew (ISO-Logical) + ISO_8859_8_I = 38598, + // ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS) + ISO_2022_JP = 50220, + // ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana) + CSISO2022JP = 50221, + // ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI) + ISO_2022_2_JP = 50222, + // ISO 2022 Korean + ISO_2022_KR = 50225, + // ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022) + X_CP50227 = 50227, + // EUC Japanese + EUC_JP_2 = 51932, + // EUC Simplified Chinese; Chinese Simplified (EUC) + EUC_CN = 51936, + // EUC Korean + EUC_KR = 51949, + // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ) + HZ_GB_2312 = 52936, + // **Windows XP and later:** GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030) + GB18030 = 54936, + // ISCII Devanagari + X_ISCII_DE = 57002, + // ISCII Bangla + X_ISCII_BE = 57003, + // ISCII Tamil + X_ISCII_TA = 57004, + // ISCII Telugu + X_ISCII_TE = 57005, + // ISCII Assamese + X_ISCII_AS = 57006, + // ISCII Odia + X_ISCII_OR = 57007, + // ISCII Kannada + X_ISCII_KA = 57008, + // ISCII Malayalam + X_ISCII_MA = 57009, + // ISCII Gujarati + X_ISCII_GU = 57010, + // ISCII Punjabi + X_ISCII_PA = 57011, + + // Unicode (UTF-7) + UTF7 = CP_UTF7, /*65000*/ + // Unicode (UTF-8) + UTF8 = CP_UTF8, /*65001*/ +} diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 6d53845de..6788ed2ea 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -7,108 +7,97 @@ foreign import gdi32 "system:Gdi32.lib" @(default_calling_convention="system") foreign gdi32 { - GetStockObject :: proc(i: c_int) -> HGDIOBJ --- + GetDeviceCaps :: proc(hdc: HDC, index: INT) -> INT --- + GetStockObject :: proc(i: INT) -> HGDIOBJ --- SelectObject :: proc(hdc: HDC, h: HGDIOBJ) -> HGDIOBJ --- DeleteObject :: proc(ho: HGDIOBJ) -> BOOL --- SetBkColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- + SetBkMode :: proc(hdc: HDC, mode: BKMODE) -> INT --- CreateCompatibleDC :: proc(hdc: HDC) -> HDC --- DeleteDC :: proc(hdc: HDC) -> BOOL --- + CancelDC :: proc(hdc: HDC) -> BOOL --- + SaveDC :: proc(hdc: HDC) -> INT --- + RestoreDC :: proc(hdc: HDC, nSavedDC: INT) -> BOOL --- CreateDIBPatternBrush :: proc(h: HGLOBAL, iUsage: UINT) -> HBRUSH --- + CreateDIBitmap :: proc(hdc: HDC, pbmih: ^BITMAPINFOHEADER, flInit: DWORD, pjBits: VOID, pbmi: ^BITMAPINFO, iUsage: UINT) -> HBITMAP --- + CreateDIBSection :: proc(hdc: HDC, pbmi: ^BITMAPINFO, usage: UINT, ppvBits: VOID, hSection: HANDLE, offset: DWORD) -> HBITMAP --- + StretchDIBits :: proc(hdc: HDC, xDest, yDest, DestWidth, DestHeight, xSrc, ySrc, SrcWidth, SrcHeight: INT, lpBits: VOID, lpbmi: ^BITMAPINFO, iUsage: UINT, rop: DWORD) -> INT --- + StretchBlt :: proc(hdcDest: HDC, xDest, yDest, wDest, hDest: INT, hdcSrc: HDC, xSrc, ySrc, wSrc, hSrc: INT, rop: DWORD) -> BOOL --- - CreateDIBitmap :: proc( - hdc: HDC, - pbmih: ^BITMAPINFOHEADER, - flInit: DWORD, - pjBits: VOID, - pbmi: ^BITMAPINFO, - iUsage: UINT, - ) -> HBITMAP --- - - CreateDIBSection :: proc( - hdc: HDC, - pbmi: ^BITMAPINFO, - usage: UINT, - ppvBits: VOID, - hSection: HANDLE, - offset: DWORD, - ) -> HBITMAP --- - - StretchDIBits :: proc( - hdc: HDC, - xDest: c_int, - yDest: c_int, - DestWidth: c_int, - DestHeight: c_int, - xSrc: c_int, - ySrc: c_int, - SrcWidth: c_int, - SrcHeight: c_int, - lpBits: VOID, - lpbmi: ^BITMAPINFO, - iUsage: UINT, - rop: DWORD, - ) -> c_int --- - - StretchBlt :: proc( - hdcDest: HDC, - xDest: c_int, - yDest: c_int, - wDest: c_int, - hDest: c_int, - hdcSrc: HDC, - xSrc: c_int, - ySrc: c_int, - wSrc: c_int, - hSrc: c_int, - rop: DWORD, - ) -> BOOL --- - - SetPixelFormat :: proc(hdc: HDC, format: c_int, ppfd: ^PIXELFORMATDESCRIPTOR) -> BOOL --- - ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int --- - DescribePixelFormat :: proc(hdc: HDC, iPixelFormat: c_int, nBytes: UINT, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int --- - SwapBuffers :: proc(HDC) -> BOOL --- + SetPixelFormat :: proc(hdc: HDC, format: INT, ppfd: ^PIXELFORMATDESCRIPTOR) -> BOOL --- + ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> INT --- + DescribePixelFormat :: proc(hdc: HDC, iPixelFormat: INT, nBytes: UINT, ppfd: ^PIXELFORMATDESCRIPTOR) -> INT --- + SwapBuffers :: proc(hdc: HDC) -> BOOL --- SetDCBrushColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- GetDCBrushColor :: proc(hdc: HDC) -> COLORREF --- - PatBlt :: proc(hdc: HDC, x, y, w, h: c_int, rop: DWORD) -> BOOL --- - Rectangle :: proc(hdc: HDC, left, top, right, bottom: c_int) -> BOOL --- + PatBlt :: proc(hdc: HDC, x, y, w, h: INT, rop: DWORD) -> BOOL --- + Rectangle :: proc(hdc: HDC, left, top, right, bottom: INT) -> BOOL --- - CreateFontW :: proc( - cHeight, cWidth, cEscapement, cOrientation, cWeight: c_int, - bItalic, bUnderline, bStrikeOut, iCharSet, iOutPrecision: DWORD, - iClipPrecision, iQuality, iPitchAndFamily: DWORD, - pszFaceName: LPCWSTR, - ) -> HFONT --- - TextOutW :: proc(hdc: HDC, x, y: c_int, lpString: LPCWSTR, c: c_int) -> BOOL --- - GetTextExtentPoint32W :: proc(hdc: HDC, lpString: LPCWSTR, c: c_int, psizl: LPSIZE) -> BOOL --- + CreateFontW :: proc(cHeight, cWidth, cEscapement, cOrientation, cWeight: INT, bItalic, bUnderline, bStrikeOut, iCharSet, iOutPrecision: DWORD, iClipPrecision, iQuality, iPitchAndFamily: DWORD, pszFaceName: LPCWSTR) -> HFONT --- + CreateFontIndirectW :: proc(lplf: ^LOGFONTW) -> HFONT --- + CreateFontIndirectExW :: proc(unnamedParam1: ^ENUMLOGFONTEXDVW) -> HFONT --- + AddFontResourceW :: proc(unnamedParam1: LPCWSTR) -> INT --- + AddFontResourceExW :: proc(name: LPCWSTR, fl: DWORD, res: PVOID) -> INT --- + AddFontMemResourceEx :: proc(pFileView: PVOID, cjSize: DWORD, pvResrved: PVOID, pNumFonts: ^DWORD) -> HANDLE --- + EnumFontsW :: proc(hdc: HDC, lpLogfont: LPCWSTR, lpProc: FONTENUMPROCW, lParam: LPARAM) -> INT --- + EnumFontFamiliesW :: proc(hdc: HDC, lpLogfont: LPCWSTR, lpProc: FONTENUMPROCW, lParam: LPARAM) -> INT --- + EnumFontFamiliesExW :: proc(hdc: HDC, lpLogfont: LPLOGFONTW, lpProc: FONTENUMPROCW, lParam: LPARAM, dwFlags: DWORD) -> INT --- + + TextOutW :: proc(hdc: HDC, x, y: INT, lpString: LPCWSTR, c: INT) -> BOOL --- + GetTextExtentPoint32W :: proc(hdc: HDC, lpString: LPCWSTR, c: INT, psizl: LPSIZE) -> BOOL --- GetTextMetricsW :: proc(hdc: HDC, lptm: LPTEXTMETRICW) -> BOOL --- CreateSolidBrush :: proc(color: COLORREF) -> HBRUSH --- - GetObjectW :: proc(h: HANDLE, c: c_int, pv: LPVOID) -> int --- - CreateCompatibleBitmap :: proc(hdc: HDC, cx, cy: c_int) -> HBITMAP --- - BitBlt :: proc(hdc: HDC, x, y, cx, cy: c_int, hdcSrc: HDC, x1, y1: c_int, rop: DWORD) -> BOOL --- - GetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start, cLines: UINT, lpvBits: LPVOID, lpbmi: ^BITMAPINFO, usage: UINT) -> int --- + GetObjectW :: proc(h: HANDLE, c: INT, pv: LPVOID) -> int --- + CreateCompatibleBitmap :: proc(hdc: HDC, cx, cy: INT) -> HBITMAP --- + BitBlt :: proc(hdc: HDC, x, y, cx, cy: INT, hdcSrc: HDC, x1, y1: INT, rop: DWORD) -> BOOL --- + GetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start, cLines: UINT, lpvBits: LPVOID, lpbmi: ^BITMAPINFO, usage: UINT) -> INT --- + SetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start: UINT, cLines: UINT, lpBits: VOID, lpbmi: ^BITMAPINFO, ColorUse: UINT) -> INT --- + SetDIBColorTable :: proc(hdc: HDC, iStart: UINT, cEntries: UINT, prgbq: ^RGBQUAD) -> UINT --- + GetDIBColorTable :: proc(hdc: HDC, iStart: UINT, cEntries: UINT, prgbq: ^RGBQUAD) -> UINT --- + + CreatePen :: proc(iStyle, cWidth: INT, color: COLORREF) -> HPEN --- + ExtCreatePen :: proc(iPenStyle, cWidth: DWORD, plbrush: ^LOGBRUSH, cStyle: DWORD, pstyle: ^DWORD) -> HPEN --- + SetDCPenColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- + GetDCPenColor :: proc(hdc: HDC) -> COLORREF --- + + CreatePalette :: proc(plpal: ^LOGPALETTE) -> HPALETTE --- + SelectPalette :: proc(hdc: HDC, hPal: HPALETTE, bForceBkgd: BOOL) -> HPALETTE --- + RealizePalette :: proc(hdc: HDC) -> UINT --- + + SetTextColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- + RoundRect :: proc(hdc: HDC, left: INT, top: INT, right: INT, bottom: INT, width: INT, height: INT) -> BOOL --- + SetPixel :: proc(hdc: HDC, x: INT, y: INT, color: COLORREF) -> COLORREF --- + + GdiTransparentBlt :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, crTransparent: UINT) -> BOOL --- + GdiGradientFill :: proc(hdc: HDC, pVertex: PTRIVERTEX, nVertex: ULONG, pMesh: PVOID, nCount: ULONG, ulMode: ULONG) -> BOOL --- + GdiAlphaBlend :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, ftn: BLENDFUNCTION) -> BOOL --- } -RGB :: #force_inline proc "contextless" (r, g, b: u8) -> COLORREF { - return transmute(COLORREF)[4]u8{r, g, b, 0} +RGB :: #force_inline proc "contextless" (#any_int r, g, b: int) -> COLORREF { + return COLORREF(DWORD(BYTE(r)) | (DWORD(BYTE(g)) << 8) | (DWORD(BYTE(b)) << 16)) +} + +PALETTERGB :: #force_inline proc "contextless" (#any_int r, g, b: int) -> COLORREF { + return 0x02000000 | RGB(r, g, b) +} + +PALETTEINDEX :: #force_inline proc "contextless" (#any_int i: int) -> COLORREF { + return COLORREF(DWORD(0x01000000) | DWORD(WORD(i))) } FXPT2DOT30 :: distinct fixed.Fixed(i32, 30) CIEXYZ :: struct { - ciexyzX: FXPT2DOT30, - ciexyzY: FXPT2DOT30, - ciexyzZ: FXPT2DOT30, + ciexyzX, ciexyzY, ciexyzZ: FXPT2DOT30, } CIEXYZTRIPLE :: struct { - ciexyzRed: CIEXYZ, - ciexyzGreen: CIEXYZ, - ciexyzBlue: CIEXYZ, + ciexyzRed, ciexyzGreen, ciexyzBlue: CIEXYZ, } // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header @@ -138,3 +127,227 @@ BITMAPV5HEADER :: struct { bV5ProfileSize: DWORD, bV5Reserved: DWORD, } + +PALETTEENTRY :: struct { + peRed, peGreen, peBlue, peFlags: BYTE, +} + +LOGPALETTE :: struct { + palVersion: WORD, + palNumEntries: WORD, + palPalEntry: []PALETTEENTRY, +} + +BKMODE :: enum { + TRANSPARENT = 1, + OPAQUE = 2, +} + +ICONINFOEXW :: struct { + cbSize: DWORD, + fIcon: BOOL, + xHotspot, yHotspot: DWORD, + hbmMask, hbmColor: HBITMAP, + wResID: WORD, + szModName: [MAX_PATH]WCHAR, + szResName: [MAX_PATH]WCHAR, +} +PICONINFOEXW :: ^ICONINFOEXW + +AC_SRC_OVER :: 0x00 +AC_SRC_ALPHA :: 0x01 + +TransparentBlt :: GdiTransparentBlt +GradientFill :: GdiGradientFill +AlphaBlend :: GdiAlphaBlend + +COLOR16 :: USHORT +TRIVERTEX :: struct { + x, y: LONG, + Red, Green, Blue, Alpha: COLOR16, +} +PTRIVERTEX :: ^TRIVERTEX + +GRADIENT_TRIANGLE :: struct { + Vertex1, Vertex2, Vertex3: ULONG, +} +PGRADIENT_TRIANGLE :: ^GRADIENT_TRIANGLE + +GRADIENT_RECT :: struct { + UpperLeft, LowerRight: ULONG, +} +PGRADIENT_RECT :: ^GRADIENT_RECT + +BLENDFUNCTION :: struct { + BlendOp, BlendFlags, SourceConstantAlpha, AlphaFormat: BYTE, +} + +GRADIENT_FILL_RECT_H : ULONG : 0x00000000 +GRADIENT_FILL_RECT_V : ULONG : 0x00000001 +GRADIENT_FILL_TRIANGLE : ULONG : 0x00000002 +GRADIENT_FILL_OP_FLAG : ULONG : 0x000000ff + +/* Brush Styles */ +BS_SOLID :: 0 +BS_NULL :: 1 +BS_HOLLOW :: BS_NULL +BS_HATCHED :: 2 +BS_PATTERN :: 3 +BS_INDEXED :: 4 +BS_DIBPATTERN :: 5 +BS_DIBPATTERNPT :: 6 +BS_PATTERN8X8 :: 7 +BS_DIBPATTERN8X8 :: 8 +BS_MONOPATTERN :: 9 + +/* Hatch Styles */ +HS_HORIZONTAL :: 0 /* ----- */ +HS_VERTICAL :: 1 /* ||||| */ +HS_FDIAGONAL :: 2 /* \\\\\ */ +HS_BDIAGONAL :: 3 /* ///// */ +HS_CROSS :: 4 /* +++++ */ +HS_DIAGCROSS :: 5 /* xxxxx */ +HS_API_MAX :: 12 + +/* Pen Styles */ +PS_SOLID :: 0 +PS_DASH :: 1 /* ------- */ +PS_DOT :: 2 /* ....... */ +PS_DASHDOT :: 3 /* _._._._ */ +PS_DASHDOTDOT :: 4 /* _.._.._ */ +PS_NULL :: 5 +PS_INSIDEFRAME :: 6 +PS_USERSTYLE :: 7 +PS_ALTERNATE :: 8 +PS_STYLE_MASK :: 0x0000000F +PS_ENDCAP_ROUND :: 0x00000000 +PS_ENDCAP_SQUARE :: 0x00000100 +PS_ENDCAP_FLAT :: 0x00000200 +PS_ENDCAP_MASK :: 0x00000F00 +PS_JOIN_ROUND :: 0x00000000 +PS_JOIN_BEVEL :: 0x00001000 +PS_JOIN_MITER :: 0x00002000 +PS_JOIN_MASK :: 0x0000F000 +PS_COSMETIC :: 0x00000000 +PS_GEOMETRIC :: 0x00010000 +PS_TYPE_MASK :: 0x000F0000 + +LOGBRUSH :: struct { + lbStyle: UINT, + lbColor: COLORREF, + lbHatch: ULONG_PTR, +} +PLOGBRUSH :: ^LOGBRUSH + +/* CombineRgn() Styles */ +RGN_AND :: 1 +RGN_OR :: 2 +RGN_XOR :: 3 +RGN_DIFF :: 4 +RGN_COPY :: 5 + +/* StretchBlt() Modes */ +// BLACKONWHITE :: 1 +// WHITEONBLACK :: 2 +// COLORONCOLOR :: 3 +// HALFTONE :: 4 + +/* PolyFill() Modes */ +ALTERNATE :: 1 +WINDING :: 2 + +/* Layout Orientation Options */ +LAYOUT_RTL :: 0x00000001 // Right to left +LAYOUT_BTT :: 0x00000002 // Bottom to top +LAYOUT_VBH :: 0x00000004 // Vertical before horizontal +LAYOUT_ORIENTATIONMASK :: (LAYOUT_RTL | LAYOUT_BTT | LAYOUT_VBH) + +/* Text Alignment Options */ +TA_NOUPDATECP :: 0 +TA_UPDATECP :: 1 + +TA_LEFT :: 0 +TA_RIGHT :: 2 +TA_CENTER :: 6 + +TA_TOP :: 0 +TA_BOTTOM :: 8 +TA_BASELINE :: 24 +TA_RTLREADING :: 256 +TA_MASK :: (TA_BASELINE+TA_CENTER+TA_UPDATECP+TA_RTLREADING) + +MM_MAX_NUMAXES :: 16 +DESIGNVECTOR :: struct { + dvReserved: DWORD, + dvNumAxes: DWORD, + dvValues: [MM_MAX_NUMAXES]LONG, +} + +LF_FACESIZE :: 32 +LF_FULLFACESIZE :: 64 + +LOGFONTW :: struct { + lfHeight: LONG, + lfWidth: LONG, + lfEscapement: LONG, + lfOrientation: LONG, + lfWeight: LONG, + lfItalic: BYTE, + lfUnderline: BYTE, + lfStrikeOut: BYTE, + lfCharSet: BYTE, + lfOutPrecision: BYTE, + lfClipPrecision: BYTE, + lfQuality: BYTE, + lfPitchAndFamily: BYTE, + lfFaceName: [LF_FACESIZE]WCHAR, +} +LPLOGFONTW :: ^LOGFONTW + +ENUMLOGFONTW :: struct { + elfLogFont: LOGFONTW, + elfFullName: [LF_FULLFACESIZE]WCHAR, + elfStyle: [LF_FACESIZE]WCHAR, +} +LPENUMLOGFONTW :: ^ENUMLOGFONTW + +ENUMLOGFONTEXW :: struct { + elfLogFont: LOGFONTW, + elfFullName: [LF_FULLFACESIZE]WCHAR, + elfStyle: [LF_FACESIZE]WCHAR, + elfScript: [LF_FACESIZE]WCHAR, +} + +ENUMLOGFONTEXDVW :: struct { + elfEnumLogfontEx: ENUMLOGFONTEXW, + elfDesignVector: DESIGNVECTOR, +} + +NEWTEXTMETRICW :: struct { + tmHeight: LONG, + tmAscent: LONG, + tmDescent: LONG, + tmInternalLeading: LONG, + tmExternalLeading: LONG, + tmAveCharWidth: LONG, + tmMaxCharWidth: LONG, + tmWeight: LONG, + tmOverhang: LONG, + tmDigitizedAspectX: LONG, + tmDigitizedAspectY: LONG, + tmFirstChar: WCHAR, + tmLastChar: WCHAR, + tmDefaultChar: WCHAR, + tmBreakChar: WCHAR, + tmItalic: BYTE, + tmUnderlined: BYTE, + tmStruckOut: BYTE, + tmPitchAndFamily: BYTE, + tmCharSet: BYTE, + ntmFlags: DWORD, + ntmSizeEM: UINT, + ntmCellHeight: UINT, + ntmAvgWidth: UINT, +} + +FONTENUMPROCW :: #type proc(lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index a29dde7b5..f2ca2e507 100755 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -51,18 +51,14 @@ foreign kernel32 { // https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents GetNumberOfConsoleInputEvents :: proc(hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> BOOL --- - GetConsoleMode :: proc(hConsoleHandle: HANDLE, - lpMode: LPDWORD) -> BOOL --- - SetConsoleMode :: proc(hConsoleHandle: HANDLE, - dwMode: DWORD) -> BOOL --- - SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE, - dwCursorPosition: COORD) -> BOOL --- - SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE, - wAttributes: WORD) -> BOOL --- - GetConsoleCP :: proc() -> UINT --- - SetConsoleCP :: proc(wCodePageID: UINT) -> BOOL --- - GetConsoleOutputCP :: proc() -> UINT --- - SetConsoleOutputCP :: proc(wCodePageID: UINT) -> BOOL --- + GetConsoleMode :: proc(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL --- + SetConsoleMode :: proc(hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL --- + SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE, dwCursorPosition: COORD) -> BOOL --- + SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE, wAttributes: WORD) -> BOOL --- + GetConsoleCP :: proc() -> CODEPAGE --- + SetConsoleCP :: proc(wCodePageID: CODEPAGE) -> BOOL --- + GetConsoleOutputCP :: proc() -> CODEPAGE --- + SetConsoleOutputCP :: proc(wCodePageID: CODEPAGE) -> BOOL --- FlushConsoleInputBuffer :: proc(hConsoleInput: HANDLE) -> BOOL --- GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL --- @@ -233,6 +229,12 @@ foreign kernel32 { QueryPerformanceCounter :: proc(lpPerformanceCount: ^LARGE_INTEGER) -> BOOL --- GetExitCodeProcess :: proc(hProcess: HANDLE, lpExitCode: LPDWORD) -> BOOL --- TerminateProcess :: proc(hProcess: HANDLE, uExitCode: UINT) -> BOOL --- + OpenProcess :: proc(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) -> HANDLE --- + OpenThread :: proc(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwThreadId: DWORD) -> HANDLE --- + GetThreadContext :: proc( + hThread: HANDLE, + lpContext: LPCONTEXT, + ) -> BOOL --- CreateProcessW :: proc( lpApplicationName: LPCWSTR, lpCommandLine: LPWSTR, @@ -250,6 +252,7 @@ foreign kernel32 { SetEnvironmentVariableW :: proc(n: LPCWSTR, v: LPCWSTR) -> BOOL --- GetEnvironmentStringsW :: proc() -> LPWCH --- FreeEnvironmentStringsW :: proc(env_ptr: LPWCH) -> BOOL --- + ExpandEnvironmentStringsW :: proc(lpSrc: LPCWSTR, lpDst: LPWSTR, nSize: DWORD) -> DWORD --- GetModuleFileNameW :: proc(hModule: HMODULE, lpFilename: LPWSTR, nSize: DWORD) -> DWORD --- CreateDirectoryW :: proc( lpPathName: LPCWSTR, @@ -434,12 +437,34 @@ foreign kernel32 { GetFileAttributesExW :: proc(lpFileName: LPCWSTR, fInfoLevelId: GET_FILEEX_INFO_LEVELS, lpFileInformation: LPVOID) -> BOOL --- GetSystemInfo :: proc(system_info: ^SYSTEM_INFO) --- GetVersionExW :: proc(osvi: ^OSVERSIONINFOEXW) --- - + GetSystemDirectoryW :: proc(lpBuffer: LPWSTR, uSize: UINT) -> UINT --- + GetWindowsDirectoryW :: proc(lpBuffer: LPWSTR, uSize: UINT) -> UINT --- + GetSystemDefaultLangID :: proc() -> LANGID --- + GetSystemDefaultLCID :: proc() -> LCID --- + GetSystemDefaultLocaleName :: proc(lpLocaleName: LPWSTR, cchLocaleName: INT) -> INT --- + LCIDToLocaleName :: proc(Locale: LCID, lpName: LPWSTR, cchName: INT, dwFlags: DWORD) -> INT --- + LocaleNameToLCID :: proc(lpName: LPCWSTR, dwFlags: DWORD) -> LCID --- + SetDllDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL --- + AddDllDirectory :: proc(NewDirectory: PCWSTR) -> rawptr --- + RemoveDllDirectory :: proc(Cookie: rawptr) -> BOOL --- LoadLibraryW :: proc(c_str: LPCWSTR) -> HMODULE --- + LoadLibraryExW :: proc(c_str: LPCWSTR, hFile: HANDLE, dwFlags: LoadLibraryEx_Flags) -> HMODULE --- FreeLibrary :: proc(h: HMODULE) -> BOOL --- GetProcAddress :: proc(h: HMODULE, c_str: LPCSTR) -> rawptr --- - LoadLibraryExW :: proc(c_str: LPCWSTR, file: HANDLE, flags: LoadLibraryEx_Flags) -> HMODULE --- + LoadResource :: proc(hModule: HMODULE, hResInfo: HRSRC) -> HGLOBAL --- + FreeResource :: proc(hResData: HGLOBAL) -> BOOL --- + LockResource :: proc(hResData: HGLOBAL) -> LPVOID --- + SizeofResource :: proc(hModule: HMODULE, hResInfo: HRSRC) -> DWORD --- + FindResourceW :: proc(hModule: HMODULE, lpName: LPCWSTR, lpType: LPCWSTR) -> HRSRC --- + FindResourceExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpName: LPCWSTR, wLanguage: LANGID) -> HRSRC --- + EnumResourceNamesW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpEnumFunc: ENUMRESNAMEPROCW, lParam: LONG_PTR) -> BOOL --- + EnumResourceNamesExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpEnumFunc: ENUMRESNAMEPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL --- + EnumResourceTypesExW :: proc(hModule: HMODULE, lpEnumFunc: ENUMRESTYPEPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL --- + EnumResourceLanguagesExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpName: LPCWSTR, lpEnumFunc: ENUMRESLANGPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL --- + LookupIconIdFromDirectory :: proc(presbits: PBYTE, fIcon: BOOL) -> INT --- + LookupIconIdFromDirectoryEx :: proc(presbits: PBYTE, fIcon: BOOL, cxDesired: INT, cyDesired: INT, Flags: UINT) -> INT --- + CreateIconFromResourceEx :: proc(presbits: PBYTE, dwResSize: DWORD, fIcon: BOOL, dwVer: DWORD, cxDesired: INT, cyDesired: INT, Flags: UINT) -> HICON --- GetFullPathNameW :: proc(filename: LPCWSTR, buffer_length: DWORD, buffer: LPCWSTR, file_part: ^LPCWSTR) -> DWORD --- GetLongPathNameW :: proc(short, long: LPCWSTR, len: DWORD) -> DWORD --- @@ -543,6 +568,45 @@ THREAD_PRIORITY_IDLE :: THREAD_BASE_PRIORITY_IDLE THREAD_MODE_BACKGROUND_BEGIN :: 0x00010000 THREAD_MODE_BACKGROUND_END :: 0x00020000 +PROCESS_ALL_ACCESS :: 0x000F0000 | SYNCHRONIZE | 0xFFFF +PROCESS_CREATE_PROCESS :: 0x0080 +PROCESS_CREATE_THREAD :: 0x0002 +PROCESS_DUP_HANDLE :: 0x0040 +PROCESS_QUERY_INFORMATION :: 0x0400 +PROCESS_QUERY_LIMITED_INFORMATION :: 0x1000 +PROCESS_SET_INFORMATION :: 0x0200 +PROCESS_SET_QUOTA :: 0x0100 +PROCESS_SUSPEND_RESUME :: 0x0800 +PROCESS_TERMINATE :: 0x0001 +PROCESS_VM_OPERATION :: 0x0008 +PROCESS_VM_READ :: 0x0010 +PROCESS_VM_WRITE :: 0x0020 + +THREAD_ALL_ACCESS :: \ + THREAD_DIRECT_IMPERSONATION | + THREAD_GET_CONTEXT | + THREAD_IMPERSONATE | + THREAD_QUERY_INFORMATION | + THREAD_QUERY_LIMITED_INFORMATION | + THREAD_SET_CONTEXT | + THREAD_SET_INFORMATION | + THREAD_SET_LIMITED_INFORMATION | + THREAD_SET_THREAD_TOKEN | + THREAD_SUSPEND_RESUME | + THREAD_TERMINATE | + SYNCHRONIZE +THREAD_DIRECT_IMPERSONATION :: 0x0200 +THREAD_GET_CONTEXT :: 0x0008 +THREAD_IMPERSONATE :: 0x0100 +THREAD_QUERY_INFORMATION :: 0x0040 +THREAD_QUERY_LIMITED_INFORMATION :: 0x0800 +THREAD_SET_CONTEXT :: 0x0010 +THREAD_SET_INFORMATION :: 0x0020 +THREAD_SET_LIMITED_INFORMATION :: 0x0400 +THREAD_SET_THREAD_TOKEN :: 0x0080 +THREAD_SUSPEND_RESUME :: 0x0002 +THREAD_TERMINATE :: 0x0001 + COPY_FILE_FAIL_IF_EXISTS :: 0x00000001 COPY_FILE_RESTARTABLE :: 0x00000002 COPY_FILE_OPEN_SOURCE_FOR_WRITE :: 0x00000004 diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 56c24f1a2..23444ff34 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -6,4 +6,254 @@ foreign import ntdll_lib "system:ntdll.lib" @(default_calling_convention="system") foreign ntdll_lib { RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- + + + NtQueryInformationProcess :: proc( + ProcessHandle: HANDLE, + ProcessInformationClass: PROCESS_INFO_CLASS, + ProcessInformation: rawptr, + ProcessInformationLength: u32, + ReturnLength: ^u32, + ) -> u32 --- + + NtQueryInformationFile :: proc( + FileHandle: HANDLE, + IoStatusBlock: PIO_STATUS_BLOCK, + FileInformation: rawptr, + Length: ULONG, + FileInformationClass: FILE_INFORMATION_CLASS, + ) -> NTSTATUS --- + + NtQueryDirectoryFileEx :: proc( + FileHandle: HANDLE, + Event: HANDLE, + ApcRoutine: PIO_APC_ROUTINE, + ApcContext: PVOID, + IoStatusBlock: PIO_STATUS_BLOCK, + FileInformation: PVOID, + Length: ULONG, + FileInformationClass: FILE_INFORMATION_CLASS, + QueryFlags: ULONG, + FileName : PUNICODE_STRING, + ) -> NTSTATUS --- +} + + +PIO_APC_ROUTINE :: #type proc "system" (ApcContext: rawptr, IoStatusBlock: PIO_STATUS_BLOCK, Reserved: ULONG) + +PIO_STATUS_BLOCK :: ^IO_STATUS_BLOCK +IO_STATUS_BLOCK :: struct { + using _: struct #raw_union { + Status: NTSTATUS, + Pointer: rawptr, + }, + Information: ULONG_PTR, +} + + +PROCESS_INFO_CLASS :: enum c_int { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29, + ProcessTelemetryIdInformation = 64, + ProcessSubsystemInformation = 75, +} + +SL_RESTART_SCAN :: 0x00000001 // The scan will start at the first entry in the directory. If this flag is not set, the scan will resume from where the last query ended. +SL_RETURN_SINGLE_ENTRY :: 0x00000002 // Normally the return buffer is packed with as many matching directory entries that fit. If this flag is set, the file system will return only one directory entry at a time. This does make the operation less efficient. +SL_INDEX_SPECIFIED :: 0x00000004 // The scan should start at a specified indexed position in the directory. This flag can only be set if you generate your own IRP_MJ_DIRECTORY_CONTROL IRP; the index is specified in the IRP. How the position is specified varies from file system to file system. +SL_RETURN_ON_DISK_ENTRIES_ONLY :: 0x00000008 // Any file system filters that perform directory virtualization or just-in-time expansion should simply pass the request through to the file system and return entries that are currently on disk. Not all file systems support this flag. +SL_NO_CURSOR_UPDATE_QUERY :: 0x00000010 // File systems maintain per-FileObject directory cursor information. When multiple threads do queries using the same FileObject, access to the per-FileObject structure is single threaded to prevent corruption of the cursor state. This flag tells the file system to not update per-FileObject cursor state information thus allowing multiple threads to query in parallel using the same handle. It behaves as if SL_RESTART_SCAN is specified on each call. If a wild card pattern is given on the next call, the operation will not pick up where the last query ended. This allows for true asynchronous directory query support. If this flag is used inside a TxF transaction the operation will be failed. Not all file systems support this flag. + + +PFILE_INFORMATION_CLASS :: ^FILE_INFORMATION_CLASS +FILE_INFORMATION_CLASS :: enum c_int { + FileDirectoryInformation = 1, + FileFullDirectoryInformation = 2, + FileBothDirectoryInformation = 3, + FileBasicInformation = 4, + FileStandardInformation = 5, + FileInternalInformation = 6, + FileEaInformation = 7, + FileAccessInformation = 8, + FileNameInformation = 9, + FileRenameInformation = 10, + FileLinkInformation = 11, + FileNamesInformation = 12, + FileDispositionInformation = 13, + FilePositionInformation = 14, + FileFullEaInformation = 15, + FileModeInformation = 16, + FileAlignmentInformation = 17, + FileAllInformation = 18, + FileAllocationInformation = 19, + FileEndOfFileInformation = 20, + FileAlternateNameInformation = 21, + FileStreamInformation = 22, + FilePipeInformation = 23, + FilePipeLocalInformation = 24, + FilePipeRemoteInformation = 25, + FileMailslotQueryInformation = 26, + FileMailslotSetInformation = 27, + FileCompressionInformation = 28, + FileObjectIdInformation = 29, + FileCompletionInformation = 30, + FileMoveClusterInformation = 31, + FileQuotaInformation = 32, + FileReparsePointInformation = 33, + FileNetworkOpenInformation = 34, + FileAttributeTagInformation = 35, + FileTrackingInformation = 36, + FileIdBothDirectoryInformation = 37, + FileIdFullDirectoryInformation = 38, + FileValidDataLengthInformation = 39, + FileShortNameInformation = 40, + FileIoCompletionNotificationInformation = 41, + FileIoStatusBlockRangeInformation = 42, + FileIoPriorityHintInformation = 43, + FileSfioReserveInformation = 44, + FileSfioVolumeInformation = 45, + FileHardLinkInformation = 46, + FileProcessIdsUsingFileInformation = 47, + FileNormalizedNameInformation = 48, + FileNetworkPhysicalNameInformation = 49, + FileIdGlobalTxDirectoryInformation = 50, + FileIsRemoteDeviceInformation = 51, + FileUnusedInformation = 52, + FileNumaNodeInformation = 53, + FileStandardLinkInformation = 54, + FileRemoteProtocolInformation = 55, + FileRenameInformationBypassAccessCheck = 56, + FileLinkInformationBypassAccessCheck = 57, + FileVolumeNameInformation = 58, + FileIdInformation = 59, + FileIdExtdDirectoryInformation = 60, + FileReplaceCompletionInformation = 61, + FileHardLinkFullIdInformation = 62, + FileIdExtdBothDirectoryInformation = 63, + FileDispositionInformationEx = 64, + FileRenameInformationEx = 65, + FileRenameInformationExBypassAccessCheck = 66, + FileDesiredStorageClassInformation = 67, + FileStatInformation = 68, + FileMemoryPartitionInformation = 69, + FileStatLxInformation = 70, + FileCaseSensitiveInformation = 71, + FileLinkInformationEx = 72, + FileLinkInformationExBypassAccessCheck = 73, + FileStorageReserveIdInformation = 74, + FileCaseSensitiveInformationForceAccessCheck = 75, + FileKnownFolderInformation = 76, + FileStatBasicInformation = 77, + FileId64ExtdDirectoryInformation = 78, + FileId64ExtdBothDirectoryInformation = 79, + FileIdAllExtdDirectoryInformation = 80, + FileIdAllExtdBothDirectoryInformation = 81, + FileStreamReservationInformation, + FileMupProviderInfo, + FileMaximumInformation, +} + +PFILE_ID_FULL_DIR_INFORMATION :: ^FILE_ID_FULL_DIR_INFORMATION +FILE_ID_FULL_DIR_INFORMATION :: struct { + NextEntryOffset: ULONG, + FileIndex: ULONG, + CreationTime: LARGE_INTEGER, + LastAccessTime: LARGE_INTEGER, + LastWriteTime: LARGE_INTEGER, + ChangeTime: LARGE_INTEGER, + EndOfFile: LARGE_INTEGER, + AllocationSize: LARGE_INTEGER, + FileAttributes: ULONG, + FileNameLength: ULONG, + EaSize: ULONG, + FileId: LARGE_INTEGER, + FileName: [1]WCHAR, +} + + +PROCESS_BASIC_INFORMATION :: struct { + ExitStatus: NTSTATUS, + PebBaseAddress: ^PEB, + AffinityMask: ULONG_PTR, + BasePriority: KPRIORITY, + UniqueProcessId: ULONG_PTR, + InheritedFromUniqueProcessId: ULONG_PTR, +} + +KPRIORITY :: rawptr + +PPS_POST_PROCESS_INIT_ROUTINE :: proc "system" () + + +PEB :: struct { + _: [2]u8, + BeingDebugged: u8, + _: [1]u8, + _: [2]rawptr, + Ldr: ^PEB_LDR_DATA, + ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS, + _: [104]u8, + _: [52]rawptr, + PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE, + _: [128]u8, + _: [1]rawptr, + SessionId: u32, +} + + + + +PEB_LDR_DATA :: struct { + _: [8]u8, + _: [3]rawptr, + InMemoryOrderModuleList: LIST_ENTRY, +} + +RTL_USER_PROCESS_PARAMETERS :: struct { + MaximumLength: u32, + Length: u32, + Flags: u32, + DebugFlags: u32, + ConsoleHandle: rawptr, + ConsoleFlags: u32, + StdInputHandle: rawptr, + StdOutputHandle: rawptr, + StdErrorHandle: rawptr, + CurrentDirectoryPath: UNICODE_STRING, + CurrentDirectoryHandle: rawptr, + DllPath: UNICODE_STRING, + ImagePathName: UNICODE_STRING, + CommandLine: UNICODE_STRING, + Environment: rawptr, + StartingPositionLeft: u32, + StartingPositionTop: u32, + Width: u32, + Height: u32, + CharWidth: u32, + CharHeight: u32, + ConsoleTextAttributes: u32, + WindowFlags: u32, + ShowWindowFlags: u32, + WindowTitle: UNICODE_STRING, + DesktopName: UNICODE_STRING, + ShellInfo: UNICODE_STRING, + RuntimeData: UNICODE_STRING, + DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR, + EnvironmentSize: u32, +} + +RTL_DRIVE_LETTER_CURDIR :: struct { + Flags: u16, + Length: u16, + TimeStamp: u32, + DosPath: UNICODE_STRING, +} + + +LIST_ENTRY :: struct { + Flink: ^LIST_ENTRY, + Blink: ^LIST_ENTRY, } \ No newline at end of file diff --git a/core/sys/windows/ole32.odin b/core/sys/windows/ole32.odin index d344db5f0..8535a6f87 100644 --- a/core/sys/windows/ole32.odin +++ b/core/sys/windows/ole32.odin @@ -7,13 +7,13 @@ foreign import "system:Ole32.lib" /* typedef enum tagCOINIT { - COINIT_APARTMENTTHREADED = 0x2, // Apartment model + COINIT_APARTMENTTHREADED = 0x2, // Apartment model #if (_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM) // DCOM - // These constants are only valid on Windows NT 4.0 - COINIT_MULTITHREADED = COINITBASE_MULTITHREADED, - COINIT_DISABLE_OLE1DDE = 0x4, // Don't use DDE for Ole1 support. - COINIT_SPEED_OVER_MEMORY = 0x8, // Trade memory for speed. + // These constants are only valid on Windows NT 4.0 + COINIT_MULTITHREADED = COINITBASE_MULTITHREADED, + COINIT_DISABLE_OLE1DDE = 0x4, // Don't use DDE for Ole1 support. + COINIT_SPEED_OVER_MEMORY = 0x8, // Trade memory for speed. #endif // DCOM } COINIT; */ @@ -26,9 +26,11 @@ COINIT :: enum DWORD { } IUnknown :: struct { - using Vtbl: ^IUnknownVtbl, + using _iunknown_vtable: ^IUnknown_VTable, } -IUnknownVtbl :: struct { + +IUnknownVtbl :: IUnknown_VTable +IUnknown_VTable :: struct { QueryInterface: proc "system" (This: ^IUnknown, riid: REFIID, ppvObject: ^rawptr) -> HRESULT, AddRef: proc "system" (This: ^IUnknown) -> ULONG, Release: proc "system" (This: ^IUnknown) -> ULONG, @@ -38,7 +40,8 @@ LPUNKNOWN :: ^IUnknown @(default_calling_convention="system") foreign Ole32 { - CoInitializeEx :: proc(reserved: rawptr, co_init: COINIT) -> HRESULT --- + CoInitialize :: proc(reserved: rawptr = nil) -> HRESULT --- + CoInitializeEx :: proc(reserved: rawptr = nil, co_init: COINIT = .APARTMENTTHREADED) -> HRESULT --- CoUninitialize :: proc() --- CoCreateInstance :: proc( @@ -50,4 +53,17 @@ foreign Ole32 { ) -> HRESULT --- CoTaskMemFree :: proc(pv: rawptr) --- + + CLSIDFromProgID :: proc(lpszProgID: LPCOLESTR, lpclsid: LPCLSID) -> HRESULT --- + CLSIDFromProgIDEx :: proc(lpszProgID, LPCOLESTR, lpclsid: LPCLSID) -> HRESULT --- + CLSIDFromString :: proc(lpsz: LPOLESTR, pclsid: LPCLSID) -> HRESULT --- + IIDFromString :: proc(lpsz: LPOLESTR, lpiid: LPIID) -> HRESULT --- + ProgIDFromCLSID :: proc(clsid: REFCLSID, lplpszProgID: ^LPOLESTR) -> HRESULT --- + StringFromCLSID :: proc(rclsid: REFCLSID, lplpsz: ^LPOLESTR) -> HRESULT --- + StringFromGUID2 :: proc(rclsid: REFCLSID, lplpsz: LPOLESTR, cchMax: INT) -> INT --- + StringFromIID :: proc(rclsid: REFIID, lplpsz: ^LPOLESTR) -> HRESULT --- + + PropVariantClear :: proc(pvar: ^PROPVARIANT) -> HRESULT --- + PropVariantCopy :: proc(pvarDest: ^PROPVARIANT, pvarSrc: ^PROPVARIANT) -> HRESULT --- + FreePropVariantArray :: proc(cVariants: ULONG, rgvars: ^PROPVARIANT) -> HRESULT --- } diff --git a/core/sys/windows/shcore.odin b/core/sys/windows/shcore.odin new file mode 100644 index 000000000..54f67989e --- /dev/null +++ b/core/sys/windows/shcore.odin @@ -0,0 +1,25 @@ +// +build windows + +package sys_windows + +foreign import shcore "system:Shcore.lib" + +@(default_calling_convention="system") +foreign shcore { + GetProcessDpiAwareness :: proc(hprocess: HANDLE, value: ^PROCESS_DPI_AWARENESS) -> HRESULT --- + SetProcessDpiAwareness :: proc(value: PROCESS_DPI_AWARENESS) -> HRESULT --- + GetDpiForMonitor :: proc(hmonitor: HMONITOR, dpiType: MONITOR_DPI_TYPE, dpiX: ^UINT, dpiY: ^UINT) -> HRESULT --- +} + +PROCESS_DPI_AWARENESS :: enum DWORD { + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2, +} + +MONITOR_DPI_TYPE :: enum DWORD { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT, +} diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 25923ded3..7340ae4d4 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -30,6 +30,12 @@ foreign shell32 { SHGetKnownFolderIDList :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppidl: rawptr) -> HRESULT --- SHSetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, pszPath: PCWSTR ) -> HRESULT --- SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> HRESULT --- + + ExtractIconExW :: proc(pszFile: LPCWSTR, nIconIndex: INT, phiconLarge: ^HICON, phiconSmall: ^HICON, nIcons: UINT) -> UINT --- + DragAcceptFiles :: proc(hWnd: HWND, fAccept: BOOL) --- + DragQueryPoint :: proc(hDrop: HDROP, ppt: ^POINT) -> BOOL --- + DragQueryFileW :: proc(hDrop: HDROP, iFile: UINT, lpszFile: LPWSTR, cch: UINT) -> UINT --- + DragFinish :: proc(hDrop: HDROP) --- // @New } APPBARDATA :: struct { @@ -67,6 +73,8 @@ ABE_BOTTOM :: 3 KNOWNFOLDERID :: GUID REFKNOWNFOLDERID :: ^KNOWNFOLDERID +HDROP :: HANDLE + KNOWN_FOLDER_FLAG :: enum u32 { DEFAULT = 0x00000000, diff --git a/core/sys/windows/tlhelp.odin b/core/sys/windows/tlhelp.odin index 7e224dd05..45d5a3ff9 100644 --- a/core/sys/windows/tlhelp.odin +++ b/core/sys/windows/tlhelp.odin @@ -68,7 +68,7 @@ MODULEENTRY32W :: struct { th32ProcessID: DWORD, GlblcntUsage: DWORD, ProccntUsage: DWORD, - modBaseAddr: BYTE, + modBaseAddr: ^BYTE, modBaseSize: DWORD, hModule: HMODULE, szModule: [MAX_MODULE_NAME32 + 1]WCHAR, diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index e568a7bc7..e10e53cf9 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -30,8 +30,10 @@ HICON :: distinct HANDLE HCURSOR :: distinct HANDLE HMENU :: distinct HANDLE HBRUSH :: distinct HANDLE +HPEN :: distinct HANDLE HGDIOBJ :: distinct HANDLE HBITMAP :: distinct HANDLE +HPALETTE :: distinct HANDLE HGLOBAL :: distinct HANDLE HHOOK :: distinct HANDLE HWINEVENTHOOK :: distinct HANDLE @@ -39,6 +41,9 @@ HKEY :: distinct HANDLE HDESK :: distinct HANDLE HFONT :: distinct HANDLE HRGN :: distinct HANDLE +HRSRC :: distinct HANDLE +HWINSTA :: distinct HANDLE +HACCEL :: distinct HANDLE BOOL :: distinct b32 BYTE :: distinct u8 BOOLEAN :: distinct b8 @@ -64,6 +69,7 @@ LONG_PTR :: int UINT_PTR :: uintptr ULONG :: c_ulong ULONGLONG :: c_ulonglong +LONGLONG :: c_longlong UCHAR :: BYTE NTSTATUS :: c.long COLORREF :: DWORD @@ -138,11 +144,14 @@ LPSTR :: ^CHAR LPWSTR :: ^WCHAR OLECHAR :: WCHAR LPOLESTR :: ^OLECHAR +LPCOLESTR :: LPCSTR LPFILETIME :: ^FILETIME LPWSABUF :: ^WSABUF LPWSAOVERLAPPED :: distinct rawptr LPWSAOVERLAPPED_COMPLETION_ROUTINE :: distinct rawptr LPCVOID :: rawptr +SCODE :: LONG +PSCODE :: ^SCODE PACCESS_TOKEN :: PVOID PSECURITY_DESCRIPTOR :: PVOID @@ -698,6 +707,92 @@ FW_BLACK :: FW_HEAVY PTIMERAPCROUTINE :: #type proc "system" (lpArgToCompletionRoutine: LPVOID, dwTimerLowValue, dwTimerHighValue: DWORD) +// Character Sets +ANSI_CHARSET :: 0 +DEFAULT_CHARSET :: 1 +SYMBOL_CHARSET :: 2 +SHIFTJIS_CHARSET :: 128 +HANGEUL_CHARSET :: 129 +HANGUL_CHARSET :: 129 +GB2312_CHARSET :: 134 +CHINESEBIG5_CHARSET :: 136 +OEM_CHARSET :: 255 +JOHAB_CHARSET :: 130 +HEBREW_CHARSET :: 177 +ARABIC_CHARSET :: 178 +GREEK_CHARSET :: 161 +TURKISH_CHARSET :: 162 +VIETNAMESE_CHARSET :: 163 +THAI_CHARSET :: 222 +EASTEUROPE_CHARSET :: 238 +RUSSIAN_CHARSET :: 204 +MAC_CHARSET :: 77 +BALTIC_CHARSET :: 186 + +// Font Signature Bitmaps +FS_LATIN1 :: 0x00000001 +FS_LATIN2 :: 0x00000002 +FS_CYRILLIC :: 0x00000004 +FS_GREEK :: 0x00000008 +FS_TURKISH :: 0x00000010 +FS_HEBREW :: 0x00000020 +FS_ARABIC :: 0x00000040 +FS_BALTIC :: 0x00000080 +FS_VIETNAMESE :: 0x00000100 +FS_THAI :: 0x00010000 +FS_JISJAPAN :: 0x00020000 +FS_CHINESESIMP :: 0x00040000 +FS_WANSUNG :: 0x00080000 +FS_CHINESETRAD :: 0x00100000 +FS_JOHAB :: 0x00200000 +FS_SYMBOL :: 0x80000000 + +// Output Precisions +OUT_DEFAULT_PRECIS :: 0 +OUT_STRING_PRECIS :: 1 +OUT_CHARACTER_PRECIS :: 2 +OUT_STROKE_PRECIS :: 3 +OUT_TT_PRECIS :: 4 +OUT_DEVICE_PRECIS :: 5 +OUT_RASTER_PRECIS :: 6 +OUT_TT_ONLY_PRECIS :: 7 +OUT_OUTLINE_PRECIS :: 8 +OUT_SCREEN_OUTLINE_PRECIS :: 9 +OUT_PS_ONLY_PRECIS :: 10 + +// Clipping Precisions +CLIP_DEFAULT_PRECIS :: 0 +CLIP_CHARACTER_PRECIS :: 1 +CLIP_STROKE_PRECIS :: 2 +CLIP_MASK :: 0xf +CLIP_LH_ANGLES :: 1 << 4 +CLIP_TT_ALWAYS :: 2 << 4 +CLIP_DFA_DISABLE :: 4 << 4 +CLIP_EMBEDDED :: 8 << 4 + +// Output Qualities +DEFAULT_QUALITY :: 0 +DRAFT_QUALITY :: 1 +PROOF_QUALITY :: 2 +NONANTIALIASED_QUALITY :: 3 +ANTIALIASED_QUALITY :: 4 +CLEARTYPE_QUALITY :: 5 +CLEARTYPE_NATURAL_QUALITY :: 6 + +// Font Pitches +DEFAULT_PITCH :: 0 +FIXED_PITCH :: 1 +VARIABLE_PITCH :: 2 +MONO_FONT :: 8 + +// Font Families +FF_DONTCARE :: 0 << 4 +FF_ROMAN :: 1 << 4 +FF_SWISS :: 2 << 4 +FF_MODERN :: 3 << 4 +FF_SCRIPT :: 4 << 4 +FF_DECORATIVE :: 5 << 4 + TIMERPROC :: #type proc "system" (HWND, UINT, UINT_PTR, DWORD) WNDPROC :: #type proc "system" (HWND, UINT, WPARAM, LPARAM) -> LRESULT @@ -931,6 +1026,16 @@ MF_RIGHTJUSTIFY :: 0x00004000 MF_MOUSESELECT :: 0x00008000 MF_END :: 0x00000080 // Obsolete -- only used by old RES files +// Menu flags for Add/Check/EnableMenuItem() +MFS_GRAYED :: 0x00000003 +MFS_DISABLED :: MFS_GRAYED +MFS_CHECKED :: MF_CHECKED +MFS_HILITE :: MF_HILITE +MFS_ENABLED :: MF_ENABLED +MFS_UNCHECKED :: MF_UNCHECKED +MFS_UNHILITE :: MF_UNHILITE +MFS_DEFAULT :: MF_DEFAULT + // Flags for TrackPopupMenu TPM_LEFTBUTTON :: 0x0000 TPM_RIGHTBUTTON :: 0x0002 @@ -1026,16 +1131,25 @@ TRACKMOUSEEVENT :: struct { } WIN32_FIND_DATAW :: struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - dwReserved0: DWORD, - dwReserved1: DWORD, - cFileName: [260]wchar_t, // #define MAX_PATH 260 - cAlternateFileName: [14]wchar_t, + dwFileAttributes: DWORD, + ftCreationTime: FILETIME, + ftLastAccessTime: FILETIME, + ftLastWriteTime: FILETIME, + nFileSizeHigh: DWORD, + nFileSizeLow: DWORD, + dwReserved0: DWORD, + dwReserved1: DWORD, + cFileName: [MAX_PATH]WCHAR, + cAlternateFileName: [14]WCHAR, +} + +FILE_ID_128 :: struct { + Identifier: [16]BYTE, +} + +FILE_ID_INFO :: struct { + VolumeSerialNumber: ULONGLONG, + FileId: FILE_ID_128, } CREATESTRUCTA :: struct { @@ -1091,6 +1205,11 @@ NMHDR :: struct { code: UINT, // NM_ code } +NCCALCSIZE_PARAMS :: struct { + rgrc: [3]RECT, + lppos: PWINDOWPOS, +} + // Generic WM_NOTIFY notification codes NM_OUTOFMEMORY :: ~uintptr(0) // -1 NM_CLICK :: NM_OUTOFMEMORY-1 // uses NMCLICK struct @@ -2145,6 +2264,7 @@ SECURITY_IMPERSONATION_LEVEL :: enum { SECURITY_INFORMATION :: DWORD ANYSIZE_ARRAY :: 1 +PLUID_AND_ATTRIBUTES :: ^LUID_AND_ATTRIBUTES LUID_AND_ATTRIBUTES :: struct { Luid: LUID, Attributes: DWORD, @@ -2188,6 +2308,14 @@ CP_SYMBOL :: 42 // SYMBOL translations CP_UTF7 :: 65000 // UTF-7 translation CP_UTF8 :: 65001 // UTF-8 translation +LCID :: DWORD +LANGID :: WORD + +LANG_NEUTRAL :: 0x00 +LANG_INVARIANT :: 0x7f +SUBLANG_NEUTRAL :: 0x00 // language neutral +SUBLANG_DEFAULT :: 0x01 // user default + MB_ERR_INVALID_CHARS :: 8 WC_ERR_INVALID_CHARS :: 128 @@ -2204,6 +2332,7 @@ FILE_TYPE_PIPE :: 0x0003 RECT :: struct {left, top, right, bottom: LONG} POINT :: struct {x, y: LONG} +PWINDOWPOS :: ^WINDOWPOS WINDOWPOS :: struct { hwnd: HWND, hwndInsertAfter: HWND, @@ -2402,8 +2531,10 @@ REFIID :: ^GUID REFGUID :: GUID IID :: GUID +LPIID :: ^IID CLSID :: GUID REFCLSID :: ^CLSID +LPCLSID :: ^CLSID CLSCTX_INPROC_SERVER :: 0x1 CLSCTX_INPROC_HANDLER :: 0x2 @@ -2433,6 +2564,7 @@ CLSCTX_RESERVED6 :: 0x1000000 CLSCTX_ACTIVATE_ARM32_SERVER :: 0x2000000 CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000 CLSCTX_PS_DLL :: 0x80000000 +CLSCTX_ALL :: CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER WSAPROTOCOLCHAIN :: struct { ChainLen: c_int, @@ -2492,10 +2624,11 @@ OBJECT_ATTRIBUTES :: struct { SecurityQualityOfService: rawptr, } +PUNICODE_STRING :: ^UNICODE_STRING UNICODE_STRING :: struct { - Length: u16, - MaximumLength: u16, - Buffer: ^u16, + Length: u16 `fmt:"-"`, + MaximumLength: u16 `fmt:"-"`, + Buffer: [^]u16 `fmt:"s,Length"`, } OVERLAPPED :: struct { @@ -2570,45 +2703,177 @@ EXCEPTION_RECORD :: struct { ExceptionInformation: [EXCEPTION_MAXIMUM_PARAMETERS]LPVOID, } -CONTEXT :: struct{} // TODO(bill) + +CONTEXT :: struct { + P1Home: DWORD64, + P2Home: DWORD64, + P3Home: DWORD64, + P4Home: DWORD64, + P5Home: DWORD64, + P6Home: DWORD64, + ContextFlags: DWORD, + MxCsr: DWORD, + SegCs: WORD, + SegDs: WORD, + SegEs: WORD, + SegFs: WORD, + SegGs: WORD, + SegSs: WORD, + EFlags: DWORD, + Dr0: DWORD64, + Dr1: DWORD64, + Dr2: DWORD64, + Dr3: DWORD64, + Dr6: DWORD64, + Dr7: DWORD64, + Rax: DWORD64, + Rcx: DWORD64, + Rdx: DWORD64, + Rbx: DWORD64, + Rsp: DWORD64, + Rbp: DWORD64, + Rsi: DWORD64, + Rdi: DWORD64, + R8: DWORD64, + R9: DWORD64, + R10: DWORD64, + R11: DWORD64, + R12: DWORD64, + R13: DWORD64, + R14: DWORD64, + R15: DWORD64, + Rip: DWORD64, + _: struct #raw_union { + FltSave: XMM_SAVE_AREA32, + Q: [16]NEON128, + D: [32]ULONGLONG, + _: struct { + Header: [2]M128A, + Legacy: [8]M128A, + Xmm0: M128A, + Xmm1: M128A, + Xmm2: M128A, + Xmm3: M128A, + Xmm4: M128A, + Xmm5: M128A, + Xmm6: M128A, + Xmm7: M128A, + Xmm8: M128A, + Xmm9: M128A, + Xmm10: M128A, + Xmm11: M128A, + Xmm12: M128A, + Xmm13: M128A, + Xmm14: M128A, + Xmm15: M128A, + }, + S: [32]DWORD, + }, + VectorRegister: [26]M128A, + VectorControl: DWORD64, + DebugControl: DWORD64, + LastBranchToRip: DWORD64, + LastBranchFromRip: DWORD64, + LastExceptionToRip: DWORD64, + LastExceptionFromRip: DWORD64, +} + +PCONTEXT :: ^CONTEXT +LPCONTEXT :: ^CONTEXT + +when size_of(uintptr) == 32 { + XSAVE_FORMAT :: struct #align(16) { + ControlWord: WORD, + StatusWord: WORD, + TagWord: BYTE, + Reserved1: BYTE, + ErrorOpcode: WORD, + ErrorOffset: DWORD, + ErrorSelector: WORD, + Reserved2: WORD, + DataOffset: DWORD, + DataSelector: WORD, + Reserved3: WORD, + MxCsr: DWORD, + MxCsr_Mask: DWORD, + FloatRegisters: [8]M128A, + // 32-bit specific + XmmRegisters: [8]M128A, + Reserved4: [192]BYTE, + StackControl: [7]DWORD, + Cr0NpxState: DWORD, + } +} else { + XSAVE_FORMAT :: struct #align(16) { + ControlWord: WORD, + StatusWord: WORD, + TagWord: BYTE, + Reserved1: BYTE, + ErrorOpcode: WORD, + ErrorOffset: DWORD, + ErrorSelector: WORD, + Reserved2: WORD, + DataOffset: DWORD, + DataSelector: WORD, + Reserved3: WORD, + MxCsr: DWORD, + MxCsr_Mask: DWORD, + FloatRegisters: [8]M128A, + // 64-bit specific + XmmRegisters: [16]M128A, + Reserved4: [96]BYTE, + } +} + +XMM_SAVE_AREA32 :: XSAVE_FORMAT + +M128A :: struct { + Low: ULONGLONG, + High: LONGLONG, +} + +NEON128 :: struct { + Low: ULONGLONG, + High: LONGLONG, +} EXCEPTION_POINTERS :: struct { ExceptionRecord: ^EXCEPTION_RECORD, - ContextRecord: ^CONTEXT, + ContextRecord: ^CONTEXT, } PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG CONSOLE_READCONSOLE_CONTROL :: struct { - nLength: ULONG, - nInitialChars: ULONG, - dwCtrlWakeupMask: ULONG, + nLength: ULONG, + nInitialChars: ULONG, + dwCtrlWakeupMask: ULONG, dwControlKeyState: ULONG, } PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL BY_HANDLE_FILE_INFORMATION :: struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, + dwFileAttributes: DWORD, + ftCreationTime: FILETIME, + ftLastAccessTime: FILETIME, + ftLastWriteTime: FILETIME, dwVolumeSerialNumber: DWORD, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - nNumberOfLinks: DWORD, - nFileIndexHigh: DWORD, - nFileIndexLow: DWORD, + nFileSizeHigh: DWORD, + nFileSizeLow: DWORD, + nNumberOfLinks: DWORD, + nFileIndexHigh: DWORD, + nFileIndexLow: DWORD, } LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION FILE_STANDARD_INFO :: struct { AllocationSize: LARGE_INTEGER, - EndOfFile: LARGE_INTEGER, - NumberOfLinks: DWORD, - DeletePending: BOOLEAN, - Directory: BOOLEAN, + EndOfFile: LARGE_INTEGER, + NumberOfLinks: DWORD, + DeletePending: BOOLEAN, + Directory: BOOLEAN, } FILE_ATTRIBUTE_TAG_INFO :: struct { @@ -2733,23 +2998,6 @@ PROFILEINFOW :: struct { hProfile: HANDLE, } -// Used in LookupAccountNameW -SID_NAME_USE :: distinct DWORD - -SID_TYPE :: enum SID_NAME_USE { - User = 1, - Group, - Domain, - Alias, - WellKnownGroup, - DeletedAccount, - Invalid, - Unknown, - Computer, - Label, - LogonSession, -} - SECURITY_MAX_SID_SIZE :: 68 // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid @@ -4254,7 +4502,7 @@ DNS_STATUS :: distinct DWORD // zero is success DNS_INFO_NO_RECORDS :: 9501 DNS_QUERY_NO_RECURSION :: 0x00000004 -DNS_RECORD :: struct { +DNS_RECORD :: struct { // aka DNS_RECORDA pNext: ^DNS_RECORD, pName: cstring, wType: WORD, @@ -4298,6 +4546,10 @@ SOCKADDR :: struct { sa_data: [14]CHAR, } +ENUMRESNAMEPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lpName: LPWSTR, lParam: LONG_PTR)-> BOOL +ENUMRESTYPEPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lParam: LONG_PTR)-> BOOL +ENUMRESLANGPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lpName: LPWSTR, wIDLanguage: LANGID, lParam: LONG_PTR)-> BOOL + DTR_Control :: enum byte { Disable = 0, Enable = 1, diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index 3bc083acb..02cf0a54d 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -6,19 +6,18 @@ foreign import user32 "system:User32.lib" @(default_calling_convention="system") foreign user32 { - GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassNAme: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL --- - GetClassInfoExW :: proc(hInsatnce: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL --- + GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassName: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL --- + GetClassInfoExW :: proc(hInstance: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL --- - GetClassLongW :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- - SetClassLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> DWORD --- + GetClassLongW :: proc(hWnd: HWND, nIndex: INT) -> DWORD --- + SetClassLongW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG) -> DWORD --- - GetWindowLongW :: proc(hWnd: HWND, nIndex: c_int) -> LONG --- - SetWindowLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> LONG --- + GetWindowLongW :: proc(hWnd: HWND, nIndex: INT) -> LONG --- + SetWindowLongW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG) -> LONG --- - GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int --- + GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: INT) -> INT --- GetParent :: proc(hWnd: HWND) -> HWND --- - IsWindowVisible :: proc(hWnd: HWND) -> BOOL --- SetWinEventHook :: proc( eventMin, eventMax: DWORD, hmodWinEventProc: HMODULE, @@ -38,10 +37,7 @@ foreign user32 { lpClassName: LPCWSTR, lpWindowName: LPCWSTR, dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, + X, Y, nWidth, nHeight: INT, hWndParent: HWND, hMenu: HMENU, hInstance: HINSTANCE, @@ -50,24 +46,33 @@ foreign user32 { DestroyWindow :: proc(hWnd: HWND) -> BOOL --- - ShowWindow :: proc(hWnd: HWND, nCmdShow: c_int) -> BOOL --- + ShowWindow :: proc(hWnd: HWND, nCmdShow: INT) -> BOOL --- IsWindow :: proc(hWnd: HWND) -> BOOL --- + IsWindowVisible :: proc(hwnd: HWND) -> BOOL --- + IsWindowEnabled :: proc(hwnd: HWND) -> BOOL --- + IsIconic :: proc(hwnd: HWND) -> BOOL --- BringWindowToTop :: proc(hWnd: HWND) -> BOOL --- GetTopWindow :: proc(hWnd: HWND) -> HWND --- SetForegroundWindow :: proc(hWnd: HWND) -> BOOL --- GetForegroundWindow :: proc() -> HWND --- + GetDesktopWindow :: proc() -> HWND --- UpdateWindow :: proc(hWnd: HWND) -> BOOL --- SetActiveWindow :: proc(hWnd: HWND) -> HWND --- GetActiveWindow :: proc() -> HWND --- RedrawWindow :: proc(hwnd: HWND, lprcUpdate: LPRECT, hrgnUpdate: HRGN, flags: RedrawWindowFlags) -> BOOL --- - + SetParent :: proc(hWndChild: HWND, hWndNewParent: HWND) -> HWND --- + SetPropW :: proc(hWnd: HWND, lpString: LPCWSTR, hData: HANDLE) -> BOOL --- + GetPropW :: proc(hWnd: HWND, lpString: LPCWSTR) -> HANDLE --- + RemovePropW :: proc(hWnd: HWND, lpString: LPCWSTR) -> HANDLE --- + EnumPropsW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW) -> INT --- + EnumPropsExW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW, lParam: LPARAM) -> INT --- GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL --- DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT --- WaitMessage :: proc() -> BOOL --- - MsgWaitForMultipleObjects :: proc(nCount: DWORD, pHandles: ^HANDLE, fWaitAll: bool, dwMilliseconds: DWORD, dwWakeMask: DWORD) -> DWORD --- + MsgWaitForMultipleObjects :: proc(nCount: DWORD, pHandles: ^HANDLE, fWaitAll: BOOL, dwMilliseconds: DWORD, dwWakeMask: DWORD) -> DWORD --- PeekMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- PeekMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- @@ -80,7 +85,7 @@ foreign user32 { PostThreadMessageA :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- PostThreadMessageW :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- - PostQuitMessage :: proc(nExitCode: c_int) --- + PostQuitMessage :: proc(nExitCode: INT) --- GetQueueStatus :: proc(flags: UINT) -> DWORD --- @@ -94,33 +99,26 @@ foreign user32 { LoadIconA :: proc(hInstance: HINSTANCE, lpIconName: LPCSTR) -> HICON --- LoadIconW :: proc(hInstance: HINSTANCE, lpIconName: LPCWSTR) -> HICON --- + GetIconInfoExW :: proc(hIcon: HICON, piconinfo: PICONINFOEXW) -> BOOL --- LoadCursorA :: proc(hInstance: HINSTANCE, lpCursorName: LPCSTR) -> HCURSOR --- LoadCursorW :: proc(hInstance: HINSTANCE, lpCursorName: LPCWSTR) -> HCURSOR --- - LoadImageW :: proc(hInst: HINSTANCE, name: LPCWSTR, type: UINT, cx: c_int, cy: c_int, fuLoad: UINT) -> HANDLE --- + LoadImageW :: proc(hInst: HINSTANCE, name: LPCWSTR, type: UINT, cx, cy: INT, fuLoad: UINT) -> HANDLE --- - CreateIcon :: proc(hInstance: HINSTANCE, nWidth: c_int, nHeight: c_int, cPlanes: BYTE, cBitsPixel: BYTE, lpbANDbits: PBYTE, lpbXORbits: PBYTE) -> HICON --- + CreateIcon :: proc(hInstance: HINSTANCE, nWidth, nHeight: INT, cPlanes: BYTE, cBitsPixel: BYTE, lpbANDbits: PBYTE, lpbXORbits: PBYTE) -> HICON --- CreateIconFromResource :: proc(presbits: PBYTE, dwResSize: DWORD, fIcon: BOOL, dwVer: DWORD) -> HICON --- DestroyIcon :: proc(hIcon: HICON) -> BOOL --- - DrawIcon :: proc(hDC: HDC, X: c_int, Y: c_int, hIcon: HICON) -> BOOL --- + DrawIcon :: proc(hDC: HDC, X, Y: INT, hIcon: HICON) -> BOOL --- - CreateCursor :: proc(hInst: HINSTANCE, xHotSpot: c_int, yHotSpot: c_int, nWidth: c_int, nHeight: c_int, pvANDPlane: PVOID, pvXORPlane: PVOID) -> HCURSOR --- + CreateCursor :: proc(hInst: HINSTANCE, xHotSpot, yHotSpot, nWidth, nHeight: INT, pvANDPlane: PVOID, pvXORPlane: PVOID) -> HCURSOR --- DestroyCursor :: proc(hCursor: HCURSOR) -> BOOL --- GetWindowRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- GetClientRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- ClientToScreen :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- ScreenToClient :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- - SetWindowPos :: proc( - hWnd: HWND, - hWndInsertAfter: HWND, - X: c_int, - Y: c_int, - cx: c_int, - cy: c_int, - uFlags: UINT, - ) -> BOOL --- - MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: c_int, bRepaint: BOOL) -> BOOL --- - GetSystemMetrics :: proc(nIndex: c_int) -> c_int --- + SetWindowPos :: proc(hWnd: HWND, hWndInsertAfter: HWND, X, Y, cx, cy: INT, uFlags: UINT) -> BOOL --- + MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: INT, bRepaint: BOOL) -> BOOL --- + GetSystemMetrics :: proc(nIndex: INT) -> INT --- AdjustWindowRect :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL) -> BOOL --- AdjustWindowRectEx :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) -> BOOL --- AdjustWindowRectExForDpi :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD, dpi: UINT) -> BOOL --- @@ -130,18 +128,36 @@ foreign user32 { GetWindowDC :: proc(hWnd: HWND) -> HDC --- GetDC :: proc(hWnd: HWND) -> HDC --- - ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> c_int --- + GetDCEx :: proc(hWnd: HWND, hrgnClip: HRGN, flags: DWORD) -> HDC --- + ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> INT --- - GetDlgCtrlID :: proc(hWnd: HWND) -> c_int --- - GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: c_int) -> HWND --- + GetDlgCtrlID :: proc(hWnd: HWND) -> INT --- + GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: INT) -> HWND --- + CreateMenu :: proc() -> HMENU --- CreatePopupMenu :: proc() -> HMENU --- + DeleteMenu :: proc(hMenu: HMENU, uPosition: UINT, uFlags: UINT) -> BOOL --- DestroyMenu :: proc(hMenu: HMENU) -> BOOL --- + InsertMenuW :: proc(hMenu: HMENU, uPosition: UINT, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL --- AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL --- + GetMenu :: proc(hWnd: HWND) -> HMENU --- SetMenu :: proc(hWnd: HWND, hMenu: HMENU) -> BOOL --- - TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x: int, y: int, nReserved: int, hWnd: HWND, prcRect: ^RECT) -> i32 --- + TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x, y: INT, nReserved: INT, hWnd: HWND, prcRect: ^RECT) -> BOOL --- RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT --- + CreateAcceleratorTableW :: proc(paccel: LPACCEL, cAccel: INT) -> HACCEL --- + DestroyAcceleratorTable :: proc(hAccel: HACCEL) -> BOOL --- + LoadAcceleratorsW :: proc(hInstance: HINSTANCE, lpTableName: LPCWSTR) -> HACCEL --- + TranslateAcceleratorW :: proc(hWnd: HWND, hAccTable: HACCEL, lpMsg: LPMSG) -> INT --- + CopyAcceleratorTableW :: proc(hAccelSrc: HACCEL, lpAccelDst: LPACCEL, cAccelEntries: INT) -> INT --- + + InsertMenuItemW :: proc(hmenu: HMENU, item: UINT, fByPosition: BOOL, lpmi: LPMENUITEMINFOW) -> BOOL --- + GetMenuItemInfoW :: proc(hmenu: HMENU, item: UINT, fByPosition: BOOL, lpmii: LPMENUITEMINFOW) -> BOOL --- + SetMenuItemInfoW :: proc(hmenu: HMENU, item: UINT, fByPositon: BOOL, lpmii: LPMENUITEMINFOW) -> BOOL --- + GetMenuDefaultItem :: proc(hMenu: HMENU, fByPos: UINT, gmdiFlags: UINT) -> UINT --- + SetMenuDefaultItem :: proc(hMenu: HMENU, uItem: UINT, fByPos: UINT) -> BOOL --- + GetMenuItemRect :: proc(hWnd: HWND, hMenu: HMENU, uItem: UINT, lprcItem: LPRECT) -> c_int --- + GetUpdateRect :: proc(hWnd: HWND, lpRect: LPRECT, bErase: BOOL) -> BOOL --- ValidateRect :: proc(hWnd: HWND, lpRect: ^RECT) -> BOOL --- InvalidateRect :: proc(hWnd: HWND, lpRect: ^RECT, bErase: BOOL) -> BOOL --- @@ -154,34 +170,35 @@ foreign user32 { ReleaseCapture :: proc() -> BOOL --- TrackMouseEvent :: proc(lpEventTrack: LPTRACKMOUSEEVENT) -> BOOL --- - GetKeyState :: proc(nVirtKey: c_int) -> SHORT --- - GetAsyncKeyState :: proc(vKey: c_int) -> SHORT --- + GetKeyState :: proc(nVirtKey: INT) -> SHORT --- + GetAsyncKeyState :: proc(vKey: INT) -> SHORT --- GetKeyboardState :: proc(lpKeyState: PBYTE) -> BOOL --- MapVirtualKeyW :: proc(uCode: UINT, uMapType: UINT) -> UINT --- - ToUnicode :: proc(nVirtKey: UINT, wScanCode: UINT, lpKeyState: ^BYTE, pwszBuff: LPWSTR, cchBuff: c_int, wFlags: UINT) -> c_int --- + ToUnicode :: proc(nVirtKey: UINT, wScanCode: UINT, lpKeyState: ^BYTE, pwszBuff: LPWSTR, cchBuff: INT, wFlags: UINT) -> INT --- - SetWindowsHookExW :: proc(idHook: c_int, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- + SetWindowsHookExW :: proc(idHook: INT, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- UnhookWindowsHookEx :: proc(hhk: HHOOK) -> BOOL --- - CallNextHookEx :: proc(hhk: HHOOK, nCode: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- + CallNextHookEx :: proc(hhk: HHOOK, nCode: INT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- SetTimer :: proc(hWnd: HWND, nIDEvent: UINT_PTR, uElapse: UINT, lpTimerFunc: TIMERPROC) -> UINT_PTR --- KillTimer :: proc(hWnd: HWND, uIDEvent: UINT_PTR) -> BOOL --- - // MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int --- - MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> c_int --- - // MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- - MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> c_int --- + // MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> INT --- + MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> INT --- + // MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> INT --- + MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> INT --- ClipCursor :: proc(lpRect: LPRECT) -> BOOL --- GetCursorPos :: proc(lpPoint: LPPOINT) -> BOOL --- - SetCursorPos :: proc(X: c_int, Y: c_int) -> BOOL --- + SetCursorPos :: proc(X, Y: INT) -> BOOL --- SetCursor :: proc(hCursor: HCURSOR) -> HCURSOR --- when !intrinsics.is_package_imported("raylib") { ShowCursor :: proc(bShow: BOOL) -> INT --- } + EnumDisplayDevicesW :: proc (lpDevice: LPCWSTR, iDevNum: DWORD, lpDisplayDevice: PDISPLAY_DEVICEW, dwFlags: DWORD) -> BOOL --- EnumDisplaySettingsW :: proc(lpszDeviceName: LPCWSTR, iModeNum: DWORD, lpDevMode: ^DEVMODEW) -> BOOL --- MonitorFromPoint :: proc(pt: POINT, dwFlags: Monitor_From_Flags) -> HMONITOR --- @@ -191,6 +208,9 @@ foreign user32 { EnumWindows :: proc(lpEnumFunc: Window_Enum_Proc, lParam: LPARAM) -> BOOL --- + IsProcessDPIAware :: proc() -> BOOL --- + SetProcessDPIAware :: proc() -> BOOL --- + SetThreadDpiAwarenessContext :: proc(dpiContext: DPI_AWARENESS_CONTEXT) -> DPI_AWARENESS_CONTEXT --- GetThreadDpiAwarenessContext :: proc() -> DPI_AWARENESS_CONTEXT --- GetWindowDpiAwarenessContext :: proc(hwnd: HWND) -> DPI_AWARENESS_CONTEXT --- @@ -225,14 +245,14 @@ foreign user32 { lpdwResult: PDWORD_PTR, ) -> LRESULT --- - GetSysColor :: proc(nIndex: c_int) -> DWORD --- - GetSysColorBrush :: proc(nIndex: c_int) -> HBRUSH --- - SetSysColors :: proc(cElements: c_int, lpaElements: ^INT, lpaRgbValues: ^COLORREF) -> BOOL --- + GetSysColor :: proc(nIndex: INT) -> DWORD --- + GetSysColorBrush :: proc(nIndex: INT) -> HBRUSH --- + SetSysColors :: proc(cElements: INT, lpaElements: ^INT, lpaRgbValues: ^COLORREF) -> BOOL --- MessageBeep :: proc(uType: UINT) -> BOOL --- IsDialogMessageW :: proc(hDlg: HWND, lpMsg: LPMSG) -> BOOL --- - GetWindowTextLengthW :: proc(hWnd: HWND) -> c_int --- - GetWindowTextW :: proc(hWnd: HWND, lpString: LPWSTR, nMaxCount: c_int) -> c_int --- + GetWindowTextLengthW :: proc(hWnd: HWND) -> INT --- + GetWindowTextW :: proc(hWnd: HWND, lpString: LPWSTR, nMaxCount: INT) -> INT --- SetWindowTextW :: proc(hWnd: HWND, lpString: LPCWSTR) -> BOOL --- CallWindowProcW :: proc(lpPrevWndFunc: WNDPROC, hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- EnableWindow :: proc(hWnd: HWND, bEnable: BOOL) -> BOOL --- @@ -245,12 +265,20 @@ foreign user32 { GetRegisteredRawInputDevices :: proc(pRawInputDevices: PRAWINPUTDEVICE, puiNumDevices: PUINT, cbSize: UINT) -> UINT --- RegisterRawInputDevices :: proc(pRawInputDevices: PCRAWINPUTDEVICE, uiNumDevices: UINT, cbSize: UINT) -> BOOL --- - SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: c_int) -> UINT --- + SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: INT) -> UINT --- SetLayeredWindowAttributes :: proc(hWnd: HWND, crKey: COLORREF, bAlpha: BYTE, dwFlags: DWORD) -> BOOL --- FillRect :: proc(hDC: HDC, lprc: ^RECT, hbr: HBRUSH) -> int --- - EqualRect :: proc(lprc1: ^RECT, lprc2: ^RECT) -> BOOL --- + EqualRect :: proc(lprc1, lprc2: ^RECT) -> BOOL --- + OffsetRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL --- + InflateRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL --- + IntersectRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL --- + SubtractRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL --- + UnionRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL --- + IsRectEmpty :: proc(lprc: ^RECT) -> BOOL --- + SetRectEmpty :: proc(lprc: ^RECT) -> BOOL --- + CopyRect :: proc(lprcDst, lprcSrc: ^RECT) -> BOOL --- GetWindowInfo :: proc(hwnd: HWND, pwi: PWINDOWINFO) -> BOOL --- GetWindowPlacement :: proc(hWnd: HWND, lpwndpl: ^WINDOWPLACEMENT) -> BOOL --- @@ -259,21 +287,34 @@ foreign user32 { CreateRectRgnIndirect :: proc(lprect: ^RECT) -> HRGN --- GetSystemMetricsForDpi :: proc(nIndex: int, dpi: UINT) -> int --- + GetCursorInfo :: proc(pci: PCURSORINFO) -> BOOL --- + GetSystemMenu :: proc(hWnd: HWND, bRevert: BOOL) -> HMENU --- EnableMenuItem :: proc(hMenu: HMENU, uIDEnableItem: UINT, uEnable: UINT) -> BOOL --- + MenuItemFromPoint :: proc(hWnd: HWND, hMenu: HMENU, ptScreen: POINT) -> INT --- DrawTextW :: proc(hdc: HDC, lpchText: LPCWSTR, cchText: INT, lprc: LPRECT, format: DrawTextFormat) -> INT --- DrawTextExW :: proc(hdc: HDC, lpchText: LPCWSTR, cchText: INT, lprc: LPRECT, format: DrawTextFormat, lpdtp: PDRAWTEXTPARAMS) -> INT --- + + GetLocaleInfoEx :: proc(lpLocaleName: LPCWSTR, LCType: LCTYPE, lpLCData: LPWSTR, cchData: INT) -> INT --- + IsValidLocaleName :: proc(lpLocaleName: LPCWSTR) -> BOOL --- + ResolveLocaleName :: proc(lpNameToResolve: LPCWSTR, lpLocaleName: LPWSTR, cchLocaleName: INT) -> INT --- + IsValidCodePage :: proc(CodePage: UINT) -> BOOL --- + GetACP :: proc() -> CODEPAGE --- + GetCPInfoExW :: proc(CodePage: CODEPAGE, dwFlags: DWORD, lpCPInfoEx: LPCPINFOEXW) -> BOOL --- + + GetProcessWindowStation :: proc() -> HWINSTA --- + GetUserObjectInformationW :: proc(hObj: HANDLE, nIndex: GetUserObjectInformationFlags, pvInfo: PVOID, nLength: DWORD, lpnLengthNeeded: LPDWORD) -> BOOL --- } CreateWindowW :: #force_inline proc "system" ( lpClassName: LPCTSTR, lpWindowName: LPCTSTR, dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, + X: INT, + Y: INT, + nWidth: INT, + nHeight: INT, hWndParent: HWND, hMenu: HMENU, hInstance: HINSTANCE, @@ -298,11 +339,11 @@ CreateWindowW :: #force_inline proc "system" ( when ODIN_ARCH == .amd64 { @(default_calling_convention="system") foreign user32 { - GetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> ULONG_PTR --- - SetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- + GetClassLongPtrW :: proc(hWnd: HWND, nIndex: INT) -> ULONG_PTR --- + SetClassLongPtrW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG_PTR) -> ULONG_PTR --- - GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- - SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- + GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: INT) -> LONG_PTR --- + SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG_PTR) -> LONG_PTR --- } } else when ODIN_ARCH == .i386 { GetClassLongPtrW :: GetClassLongW @@ -312,8 +353,8 @@ when ODIN_ARCH == .amd64 { SetWindowLongPtrW :: SetWindowLongW } -GET_SC_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> c_int { - return c_int(wParam) & 0xFFF0 +GET_SC_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> INT { + return INT(wParam) & 0xFFF0 } GET_WHEEL_DELTA_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> c_short { @@ -332,6 +373,10 @@ GET_XBUTTON_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> WORD return HIWORD(cast(DWORD)wParam) } +GET_RAWINPUT_CODE_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> BYTE { + return BYTE(wParam) & 0xFF +} + MAKEINTRESOURCEW :: #force_inline proc "contextless" (#any_int i: int) -> LPWSTR { return cast(LPWSTR)uintptr(WORD(i)) } @@ -512,8 +557,8 @@ WINDOWPLACEMENT :: struct { flags: UINT, showCmd: UINT, ptMinPosition: POINT, - ptMaxPosition: POINT, - rcNormalPosition: RECT, + ptMaxPosition: POINT, + rcNormalPosition: RECT, } WINDOWINFO :: struct { @@ -530,11 +575,20 @@ WINDOWINFO :: struct { } PWINDOWINFO :: ^WINDOWINFO +CURSORINFO :: struct { + cbSize: DWORD, + flags: DWORD, + hCursor: HCURSOR, + ptScreenPos: POINT, +} +PCURSORINFO :: ^CURSORINFO + + DRAWTEXTPARAMS :: struct { cbSize: UINT, - iTabLength: int, - iLeftMargin: int, - iRightMargin: int, + iTabLength: INT, + iLeftMargin: INT, + iRightMargin: INT, uiLengthDrawn: UINT, } PDRAWTEXTPARAMS :: ^DRAWTEXTPARAMS @@ -581,11 +635,103 @@ RedrawWindowFlags :: enum UINT { RDW_NOFRAME = 0x0800, } +GetUserObjectInformationFlags :: enum INT { + UOI_FLAGS = 1, + UOI_NAME = 2, + UOI_TYPE = 3, + UOI_USER_SID = 4, + UOI_HEAPSIZE = 5, + UOI_IO = 6, + UOI_TIMERPROC_EXCEPTION_SUPPRESSION = 7, +} + +USEROBJECTFLAGS :: struct { + fInherit: BOOL, + fReserved: BOOL, + dwFlags: DWORD, +} + +PROPENUMPROCW :: #type proc(unnamedParam1: HWND, unnamedParam2: LPCWSTR, unnamedParam3: HANDLE) -> BOOL +PROPENUMPROCEXW :: #type proc(unnamedParam1: HWND, unnamedParam2: LPCWSTR, unnamedParam3: HANDLE, unnamedParam4: ULONG_PTR) -> BOOL + +RT_CURSOR :: LPWSTR(uintptr(0x00000001)) +RT_BITMAP :: LPWSTR(uintptr(0x00000002)) +RT_ICON :: LPWSTR(uintptr(0x00000003)) +RT_MENU :: LPWSTR(uintptr(0x00000004)) +RT_DIALOG :: LPWSTR(uintptr(0x00000005)) +RT_STRING :: LPWSTR(uintptr(0x00000006)) +RT_FONTDIR :: LPWSTR(uintptr(0x00000007)) +RT_FONT :: LPWSTR(uintptr(0x00000008)) +RT_ACCELERATOR :: LPWSTR(uintptr(0x00000009)) +RT_RCDATA :: LPWSTR(uintptr(0x0000000A)) +RT_MESSAGETABLE :: LPWSTR(uintptr(0x0000000B)) +RT_GROUP_CURSOR :: LPWSTR(uintptr(0x0000000C)) +RT_GROUP_ICON :: LPWSTR(uintptr(0x0000000E)) +RT_VERSION :: LPWSTR(uintptr(0x00000010)) +RT_DLGINCLUDE :: LPWSTR(uintptr(0x00000011)) +RT_PLUGPLAY :: LPWSTR(uintptr(0x00000013)) +RT_VXD :: LPWSTR(uintptr(0x00000014)) +RT_ANICURSOR :: LPWSTR(uintptr(0x00000015)) +RT_ANIICON :: LPWSTR(uintptr(0x00000016)) +RT_MANIFEST :: LPWSTR(uintptr(0x00000018)) + +CREATEPROCESS_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000001)) +ISOLATIONAWARE_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000002)) +ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000003)) +ISOLATIONPOLICY_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000004)) +ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000005)) +MINIMUM_RESERVED_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000001)) +MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000010)) + +ACCEL :: struct { + /* Also called the flags field */ + fVirt: BYTE, + key: WORD, + cmd: WORD, +} +LPACCEL :: ^ACCEL + +MIIM_STATE :: 0x00000001 +MIIM_ID :: 0x00000002 +MIIM_SUBMENU :: 0x00000004 +MIIM_CHECKMARKS :: 0x00000008 +MIIM_TYPE :: 0x00000010 +MIIM_DATA :: 0x00000020 + +MIIM_STRING :: 0x00000040 +MIIM_BITMAP :: 0x00000080 +MIIM_FTYPE :: 0x00000100 + +MENUITEMINFOW :: struct { + cbSize: UINT, + fMask: UINT, + fType: UINT, // used if MIIM_TYPE (4.0) or MIIM_FTYPE (>4.0) + fState: UINT, // used if MIIM_STATE + wID: UINT, // used if MIIM_ID + hSubMenu: HMENU, // used if MIIM_SUBMENU + hbmpChecked: HBITMAP, // used if MIIM_CHECKMARKS + hbmpUnchecked: HBITMAP, // used if MIIM_CHECKMARKS + dwItemData: ULONG_PTR, // used if MIIM_DATA + dwTypeData: LPWSTR, // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0) + cch: UINT, // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0) + hbmpItem: HBITMAP, // used if MIIM_BITMAP +} +LPMENUITEMINFOW :: ^MENUITEMINFOW +DISPLAY_DEVICEW :: struct { + cb: DWORD, + DeviceName: [32]WCHAR, + DeviceString: [128]WCHAR, + StateFlags: DWORD, + DeviceID: [128]WCHAR, + DeviceKey: [128]WCHAR, +} +PDISPLAY_DEVICEW :: ^DISPLAY_DEVICEW + // OUTOFCONTEXT is the zero value, use {} WinEventFlags :: bit_set[WinEventFlag; DWORD] WinEventFlag :: enum DWORD { - SKIPOWNTHREAD = 0, - SKIPOWNPROCESS = 1, - INCONTEXT = 2, + SKIPOWNTHREAD = 0, + SKIPOWNPROCESS = 1, + INCONTEXT = 2, } diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index c68d58de0..929df1765 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -6,22 +6,79 @@ import "base:intrinsics" L :: intrinsics.constant_utf16_cstring -LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD { +// https://learn.microsoft.com/en-us/windows/win32/winmsg/makeword +MAKEWORD :: #force_inline proc "contextless" (#any_int a, b: int) -> WORD { + return WORD(BYTE(DWORD_PTR(a) & 0xff)) | (WORD(BYTE(DWORD_PTR(b) & 0xff)) << 8) +} + +// https://learn.microsoft.com/en-us/windows/win32/winmsg/makelong +MAKELONG :: #force_inline proc "contextless" (#any_int a, b: int) -> LONG { + return LONG(WORD(DWORD_PTR(a) & 0xffff)) | (LONG(WORD(DWORD_PTR(b) & 0xffff)) << 16) +} + +// https://learn.microsoft.com/en-us/windows/win32/winmsg/loword +LOWORD :: #force_inline proc "contextless" (#any_int x: int) -> WORD { return WORD(x & 0xffff) } -HIWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD { +// https://learn.microsoft.com/en-us/windows/win32/winmsg/hiword +HIWORD :: #force_inline proc "contextless" (#any_int x: int) -> WORD { return WORD(x >> 16) } +// https://learn.microsoft.com/en-us/windows/win32/winmsg/lobyte +LOBYTE :: #force_inline proc "contextless" (w: WORD) -> BYTE { + return BYTE((DWORD_PTR(w)) & 0xff) +} + +// https://learn.microsoft.com/en-us/windows/win32/winmsg/hibyte +HIBYTE :: #force_inline proc "contextless" (w: WORD) -> BYTE { + return BYTE(((DWORD_PTR(w)) >> 8) & 0xff) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makewparam +MAKEWPARAM :: #force_inline proc "contextless" (#any_int l, h: int) -> WPARAM { + return WPARAM(MAKELONG(l, h)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makelparam +MAKELPARAM :: #force_inline proc "contextless" (#any_int l, h: int) -> LPARAM { + return LPARAM(MAKELONG(l, h)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makelresult +MAKELRESULT :: #force_inline proc "contextless" (#any_int l, h: int) -> LRESULT { + return LRESULT(MAKELONG(l, h)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/windowsx/nf-windowsx-get_x_lparam GET_X_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int { return cast(c_int)cast(c_short)LOWORD(cast(DWORD)lp) } +// https://learn.microsoft.com/en-us/windows/win32/api/windowsx/nf-windowsx-get_y_lparam GET_Y_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int { return cast(c_int)cast(c_short)HIWORD(cast(DWORD)lp) } +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-makelcid +MAKELCID :: #force_inline proc "contextless" (lgid, srtid: WORD) -> LCID { + return (DWORD(WORD(srtid)) << 16) | DWORD(WORD(lgid)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-makelangid +MAKELANGID :: #force_inline proc "contextless" (p, s: WORD) -> DWORD { + return DWORD(WORD(s)) << 10 | DWORD(WORD(p)) +} + +LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID { + return LANGID(lcid) +} + +// this one gave me trouble as it do not mask the values. +// the _ in the name is also off comparing to the c code +// i can't find any usage in the odin repo +@(deprecated = "use MAKEWORD") MAKE_WORD :: #force_inline proc "contextless" (x, y: WORD) -> WORD { return x << 8 | y } @@ -53,8 +110,8 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { return text[:n] } utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring { - if res := utf8_to_utf16(s, allocator); res != nil { - return &res[0] + if res := utf8_to_utf16(s, allocator); len(res) > 0 { + return raw_data(res) } return nil } @@ -202,7 +259,7 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s username_w := utf8_to_utf16(username, context.temp_allocator) cbsid: DWORD computer_name_size: DWORD - pe_use := SID_TYPE.User + pe_use := SID_NAME_USE.SidTypeUser res := LookupAccountNameW( nil, // Look on this computer first @@ -244,7 +301,7 @@ get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { username_w := utf8_to_utf16(username, context.temp_allocator) cbsid: DWORD computer_name_size: DWORD - pe_use := SID_TYPE.User + pe_use := SID_NAME_USE.SidTypeUser res := LookupAccountNameW( nil, // Look on this computer first diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index c66a22322..8882dad71 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -1,6 +1,189 @@ // +build windows package sys_windows +// https://learn.microsoft.com/en-us/windows/win32/api/winerror/ + +// Values are 32 bit values laid out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code + +// Define the facility codes +FACILITY :: enum DWORD { + NULL = 0, + RPC = 1, + DISPATCH = 2, + STORAGE = 3, + ITF = 4, + WIN32 = 7, + WINDOWS = 8, + SSPI = 9, + SECURITY = 9, + CONTROL = 10, + CERT = 11, + INTERNET = 12, + MEDIASERVER = 13, + MSMQ = 14, + SETUPAPI = 15, + SCARD = 16, + COMPLUS = 17, + AAF = 18, + URT = 19, + ACS = 20, + DPLAY = 21, + UMI = 22, + SXS = 23, + WINDOWS_CE = 24, + HTTP = 25, + USERMODE_COMMONLOG = 26, + WER = 27, + USERMODE_FILTER_MANAGER = 31, + BACKGROUNDCOPY = 32, + CONFIGURATION = 33, + WIA = 33, + STATE_MANAGEMENT = 34, + METADIRECTORY = 35, + WINDOWSUPDATE = 36, + DIRECTORYSERVICE = 37, + GRAPHICS = 38, + SHELL = 39, + NAP = 39, + TPM_SERVICES = 40, + TPM_SOFTWARE = 41, + UI = 42, + XAML = 43, + ACTION_QUEUE = 44, + PLA = 48, + WINDOWS_SETUP = 48, + FVE = 49, + FWP = 50, + WINRM = 51, + NDIS = 52, + USERMODE_HYPERVISOR = 53, + CMI = 54, + USERMODE_VIRTUALIZATION = 55, + USERMODE_VOLMGR = 56, + BCD = 57, + USERMODE_VHD = 58, + USERMODE_HNS = 59, + SDIAG = 60, + WEBSERVICES = 61, + WINPE = 61, + WPN = 62, + WINDOWS_STORE = 63, + INPUT = 64, + QUIC = 65, + EAP = 66, + IORING = 70, + WINDOWS_DEFENDER = 80, + OPC = 81, + XPS = 82, + MBN = 84, + POWERSHELL = 84, + RAS = 83, + P2P_INT = 98, + P2P = 99, + DAF = 100, + BLUETOOTH_ATT = 101, + AUDIO = 102, + STATEREPOSITORY = 103, + VISUALCPP = 109, + SCRIPT = 112, + PARSE = 113, + BLB = 120, + BLB_CLI = 121, + WSBAPP = 122, + BLBUI = 128, + USN = 129, + USERMODE_VOLSNAP = 130, + TIERING = 131, + WSB_ONLINE = 133, + ONLINE_ID = 134, + DEVICE_UPDATE_AGENT = 135, + DRVSERVICING = 136, + DLS = 153, + DELIVERY_OPTIMIZATION = 208, + USERMODE_SPACES = 231, + USER_MODE_SECURITY_CORE = 232, + USERMODE_LICENSING = 234, + SOS = 160, + OCP_UPDATE_AGENT = 173, + DEBUGGERS = 176, + SPP = 256, + RESTORE = 256, + DMSERVER = 256, + DEPLOYMENT_SERVICES_SERVER = 257, + DEPLOYMENT_SERVICES_IMAGING = 258, + DEPLOYMENT_SERVICES_MANAGEMENT = 259, + DEPLOYMENT_SERVICES_UTIL = 260, + DEPLOYMENT_SERVICES_BINLSVC = 261, + DEPLOYMENT_SERVICES_PXE = 263, + DEPLOYMENT_SERVICES_TFTP = 264, + DEPLOYMENT_SERVICES_TRANSPORT_MANAGEMENT = 272, + DEPLOYMENT_SERVICES_DRIVER_PROVISIONING = 278, + DEPLOYMENT_SERVICES_MULTICAST_SERVER = 289, + DEPLOYMENT_SERVICES_MULTICAST_CLIENT = 290, + DEPLOYMENT_SERVICES_CONTENT_PROVIDER = 293, + HSP_SERVICES = 296, + HSP_SOFTWARE = 297, + LINGUISTIC_SERVICES = 305, + AUDIOSTREAMING = 1094, + TTD = 1490, + ACCELERATOR = 1536, + WMAAECMA = 1996, + DIRECTMUSIC = 2168, + DIRECT3D10 = 2169, + DXGI = 2170, + DXGI_DDI = 2171, + DIRECT3D11 = 2172, + DIRECT3D11_DEBUG = 2173, + DIRECT3D12 = 2174, + DIRECT3D12_DEBUG = 2175, + DXCORE = 2176, + PRESENTATION = 2177, + LEAP = 2184, + AUDCLNT = 2185, + WINCODEC_DWRITE_DWM = 2200, + WINML = 2192, + DIRECT2D = 2201, + DEFRAG = 2304, + USERMODE_SDBUS = 2305, + JSCRIPT = 2306, + PIDGENX = 2561, + EAS = 85, + WEB = 885, + WEB_SOCKET = 886, + MOBILE = 1793, + SQLITE = 1967, + SERVICE_FABRIC = 1968, + UTC = 1989, + WEP = 2049, + SYNCENGINE = 2050, + XBOX = 2339, + GAME = 2340, + PIX = 2748, +} + ERROR_SUCCESS : DWORD : 0 NO_ERROR :: 0 SEC_E_OK : HRESULT : 0x00000000 @@ -42,14 +225,55 @@ ERROR_TIMEOUT : DWORD : 1460 ERROR_DATATYPE_MISMATCH : DWORD : 1629 ERROR_UNSUPPORTED_TYPE : DWORD : 1630 ERROR_NOT_SAME_OBJECT : DWORD : 1656 -ERROR_PIPE_CONNECTED : DWORD : 0x80070217 +ERROR_PIPE_CONNECTED : DWORD : 535 ERROR_PIPE_BUSY : DWORD : 231 -E_NOTIMPL :: HRESULT(-0x7fff_bfff) // 0x8000_4001 +// https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values +S_OK :: 0x00000000 // Operation successful +E_NOTIMPL :: 0x80004001 // Not implemented +E_NOINTERFACE :: 0x80004002 // No such interface supported +E_POINTER :: 0x80004003 // Pointer that is not valid +E_ABORT :: 0x80004004 // Operation aborted +E_FAIL :: 0x80004005 // Unspecified failure +E_UNEXPECTED :: 0x8000FFFF // Unexpected failure +E_ACCESSDENIED :: 0x80070005 // General access denied error +E_HANDLE :: 0x80070006 // Handle that is not valid +E_OUTOFMEMORY :: 0x8007000E // Failed to allocate necessary memory +E_INVALIDARG :: 0x80070057 // One or more arguments are not valid -SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= 0 } +// Severity values +SEVERITY :: enum DWORD { + SUCCESS = 0, + ERROR = 1, +} +// Generic test for success on any status value (non-negative numbers indicate success). +SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= S_OK } +// and the inverse +FAILED :: #force_inline proc(#any_int result: int) -> bool { return result < S_OK } +// Generic test for error on any status value. +IS_ERROR :: #force_inline proc(#any_int status: int) -> bool { return u32(status) >> 31 == u32(SEVERITY.ERROR) } + +// Return the code +HRESULT_CODE :: #force_inline proc(#any_int hr: int) -> int { return int(u32(hr) & 0xFFFF) } + +// Return the facility +HRESULT_FACILITY :: #force_inline proc(#any_int hr: int) -> FACILITY { return FACILITY((u32(hr) >> 16) & 0x1FFF) } + +// Return the severity +HRESULT_SEVERITY :: #force_inline proc(#any_int hr: int) -> SEVERITY { return SEVERITY((u32(hr) >> 31) & 0x1) } + +// Create an HRESULT value from component pieces +MAKE_HRESULT :: #force_inline proc(#any_int sev: int, #any_int fac: int, #any_int code: int) -> HRESULT { + return HRESULT((uint(sev)<<31) | (uint(fac)<<16) | (uint(code))) +} + +DECODE_HRESULT :: #force_inline proc(#any_int hr: int) -> (SEVERITY, FACILITY, int) { + return HRESULT_SEVERITY(hr), HRESULT_FACILITY(hr), HRESULT_CODE(hr) +} + +// aka ERROR or WIN32_ERROR to hint the WIN32 facility System_Error :: enum DWORD { // The operation completed successfully. SUCCESS = 0x0, diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin index 8ddef29c0..a1786c27a 100644 --- a/core/sys/windows/winmm.odin +++ b/core/sys/windows/winmm.odin @@ -270,7 +270,7 @@ LPHWAVEOUT :: ^HWAVEOUT // https://learn.microsoft.com/en-us/windows/win32/multimedia/multimedia-timer-structures MMTIME :: struct { - wType: UINT, + wType: MMTIME_TYPE, u: struct #raw_union { ms: DWORD, sample: DWORD, @@ -292,6 +292,21 @@ MMTIME :: struct { } LPMMTIME :: ^MMTIME +MMTIME_TYPE :: enum UINT { + /* time in milliseconds */ + TIME_MS = 0x0001, + /* number of wave samples */ + TIME_SAMPLES = 0x0002, + /* current byte offset */ + TIME_BYTES = 0x0004, + /* SMPTE time */ + TIME_SMPTE = 0x0008, + /* MIDI time */ + TIME_MIDI = 0x0010, + /* Ticks within MIDI stream */ + TIME_TICKS = 0x0020, +} + MAXPNAMELEN :: 32 MAXERRORLENGTH :: 256 MMVERSION :: UINT diff --git a/core/sys/windows/winnls.odin b/core/sys/windows/winnls.odin new file mode 100644 index 000000000..292d2fad2 --- /dev/null +++ b/core/sys/windows/winnls.odin @@ -0,0 +1,31 @@ +// +build windows +package sys_windows + +LCTYPE :: distinct DWORD + +LOCALE_NAME_MAX_LENGTH :: 85 +LOCALE_NAME_USER_DEFAULT :: 0 +LOCALE_NAME_INVARIANT : wstring = L("") +LOCALE_NAME_SYSTEM_DEFAULT : wstring = L("!x-sys-default-locale") + +// String Length Maximums. +// 5 ranges, 2 bytes ea., 0 term. +MAX_LEADBYTES :: 12 +// single or double byte +MAX_DEFAULTCHAR :: 2 + +CPINFOEXW :: struct{ + // Maximum length, in bytes, of a character in the code page. + MaxCharSize: UINT, + // The default is usually the "?" character for the code page. + DefaultChar: [MAX_DEFAULTCHAR]BYTE, + // A fixed-length array of lead byte ranges, for which the number of lead byte ranges is variable. + LeadByte: [MAX_LEADBYTES]BYTE, + // The default is usually the "?" character or the katakana middle dot character. + UnicodeDefaultChar: WCHAR, + // Code page value. This value reflects the code page passed to the GetCPInfoEx function. + CodePage: CODEPAGE, + // Full name of the code page. + CodePageName: [MAX_PATH]WCHAR, +} +LPCPINFOEXW :: ^CPINFOEXW diff --git a/core/sys/windows/winver.odin b/core/sys/windows/winver.odin new file mode 100644 index 000000000..091d53d3a --- /dev/null +++ b/core/sys/windows/winver.odin @@ -0,0 +1,92 @@ +// +build windows +package sys_windows + +foreign import version "system:version.lib" + +@(default_calling_convention = "system") +foreign version { + GetFileVersionInfoSizeW :: proc(lpwstrFilename: LPCWSTR, lpdwHandle: LPDWORD) -> DWORD --- + GetFileVersionInfoW :: proc(lptstrFilename: LPCWSTR, dwHandle: DWORD, dwLen: DWORD, lpData: LPVOID) -> BOOL --- + + GetFileVersionInfoSizeExW :: proc(dwFlags: FILE_VER_GET_FLAGS, lpwstrFilename: LPCWSTR, lpdwHandle: LPDWORD) -> DWORD --- + GetFileVersionInfoExW :: proc(dwFlags: FILE_VER_GET_FLAGS, lpwstrFilename: LPCWSTR, dwHandle, dwLen: DWORD, lpData: LPVOID) -> DWORD --- + + VerLanguageNameW :: proc(wLang: DWORD, szLang: LPWSTR, cchLang: DWORD) -> DWORD --- + VerQueryValueW :: proc(pBlock: LPCVOID, lpSubBlock: LPCWSTR, lplpBuffer: ^LPVOID, puLen: PUINT) -> BOOL --- +} + +FILE_VER_GET :: enum DWORD {LOCALISED, NEUTRAL, PREFETCHED} +FILE_VER_GET_FLAGS :: bit_set[FILE_VER_GET; DWORD] + +/* ----- Symbols ----- */ +VS_FILE_INFO :: RT_VERSION +VS_VERSION_INFO :: 1 +VS_USER_DEFINED :: 100 + +VS_FFI_SIGNATURE : DWORD : 0xFEEF04BD + +VS_FFI_STRUCVERSION :: 0x00010000 +VS_FFI_FILEFLAGSMASK :: 0x0000003F + +/* ----- VS_VERSION.dwFileFlags ----- */ +VS_FILEFLAG :: enum DWORD { + DEBUG, + PRERELEASE, + PATCHED, + PRIVATEBUILD, + INFOINFERRED, + SPECIALBUILD, +} +VS_FILEFLAGS :: bit_set[VS_FILEFLAG;DWORD] + +/* ----- VS_VERSION.dwFileOS ----- */ +VOS :: enum WORD { + UNKNOWN = 0x0000, + DOS = 0x0001, + OS216 = 0x0002, + OS232 = 0x0003, + NT = 0x0004, + WINCE = 0x0005, +} +VOS2 :: enum WORD { + BASE = 0x0000, + WINDOWS16 = 0x0001, + PM16 = 0x0002, + PM32 = 0x0003, + WINDOWS32 = 0x0004, +} + +/* ----- VS_VERSION.dwFileType ----- */ +VFT :: enum DWORD { + UNKNOWN = 0x00000000, + APP = 0x00000001, + DLL = 0x00000002, + DRV = 0x00000003, + FONT = 0x00000004, + VXD = 0x00000005, + STATIC_LIB = 0x00000007, +} + +/* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_DRV ----- */ +VFT2_WINDOWS_DRV :: enum DWORD { + UNKNOWN = 0x00000000, + DRV_PRINTER = 0x00000001, + DRV_KEYBOARD = 0x00000002, + DRV_LANGUAGE = 0x00000003, + DRV_DISPLAY = 0x00000004, + DRV_MOUSE = 0x00000005, + DRV_NETWORK = 0x00000006, + DRV_SYSTEM = 0x00000007, + DRV_INSTALLABLE = 0x00000008, + DRV_SOUND = 0x00000009, + DRV_COMM = 0x0000000A, + DRV_INPUTMETHOD = 0x0000000B, + DRV_VERSIONED_PRINTER = 0x0000000C, +} + +/* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_FONT ----- */ +VFT2_WINDOWS_FONT :: enum DWORD { + FONT_RASTER = 0x00000001, + FONT_VECTOR = 0x00000002, + FONT_TRUETYPE = 0x00000003, +} diff --git a/core/sys/windows/wow64_apiset.odin b/core/sys/windows/wow64_apiset.odin new file mode 100644 index 000000000..28558e9ca --- /dev/null +++ b/core/sys/windows/wow64_apiset.odin @@ -0,0 +1,85 @@ +//+build windows +package sys_windows + +foreign import kernel32 "system:Kernel32.lib" + +@(default_calling_convention="system") +foreign kernel32 { + GetSystemWow64Directory2W :: proc (lpBuffer: LPWSTR, uSize: UINT, ImageFileMachineTyp: WORD) -> UINT --- + GetSystemWow64DirectoryW :: proc (lpBuffer: LPWSTR, uSize: UINT) -> UINT --- + IsWow64GuestMachineSupported :: proc (WowGuestMachine: USHORT, MachineIsSupported: ^BOOL) -> HRESULT --- + IsWow64Process :: proc (hProcess: HANDLE, Wow64Process: PBOOL) -> BOOL --- + IsWow64Process2 :: proc (hProcess: HANDLE, pProcessMachine: ^USHORT, pNativeMachine: ^USHORT) -> BOOL --- + Wow64EnableWow64FsRedirection :: proc (Wow64FsEnableRedirection: BOOLEAN) -> BOOLEAN --- + Wow64DisableWow64FsRedirection :: proc (OldValue: ^PVOID) -> BOOL --- + Wow64RevertWow64FsRedirection :: proc (OlValue: PVOID) -> BOOL --- + Wow64GetThreadContext :: proc (hThread: HANDLE, lpContext: PWOW64_CONTEXT) -> BOOL --- + Wow64SetThreadContext :: proc(hThread: HANDLE, lpContext: ^WOW64_CONTEXT) -> BOOL --- + Wow64SetThreadDefaultGuestMachine :: proc(Machine: USHORT) -> USHORT --- + Wow64SuspendThread :: proc (hThread: HANDLE) -> DWORD --- +} + +WOW64_CONTEXT_i386 :: 0x00010000 + +WOW64_CONTEXT_CONTROL :: (WOW64_CONTEXT_i386 | 0x00000001) +WOW64_CONTEXT_INTEGER :: (WOW64_CONTEXT_i386 | 0x00000002) +WOW64_CONTEXT_SEGMENTS :: (WOW64_CONTEXT_i386 | 0x00000004) + +WOW64_CONTEXT_FLOATING_POINT :: (WOW64_CONTEXT_i386 | 0x00000008) +WOW64_CONTEXT_DEBUG_REGISTERS :: (WOW64_CONTEXT_i386 | 0x00000010) +WOW64_CONTEXT_EXTENDED_REGISTERS :: (WOW64_CONTEXT_i386 | 0x00000020) +WOW64_CONTEXT_FULL :: (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS) +WOW64_CONTEXT_ALL :: ( + WOW64_CONTEXT_CONTROL | + WOW64_CONTEXT_INTEGER | + WOW64_CONTEXT_SEGMENTS | + WOW64_CONTEXT_FLOATING_POINT | + WOW64_CONTEXT_DEBUG_REGISTERS | + WOW64_CONTEXT_EXTENDED_REGISTERS) + +WOW64_SIZE_OF_80387_REGISTERS :: 80 +WOW64_MAXIMUM_SUPPORTED_EXTENSION :: 512 + +WOW64_CONTEXT :: struct { + ContextFlags: DWORD, + Dr0: DWORD, + Dr1: DWORD, + Dr2: DWORD, + Dr3: DWORD, + Dr6: DWORD, + Dr7: DWORD, + FloatSave: WOW64_FLOATING_SAVE_AREA, + SegGs: DWORD, + SegFs: DWORD, + SegEs: DWORD, + SegDs: DWORD, + Edi: DWORD, + Esi: DWORD, + Ebx: DWORD, + Edx: DWORD, + Ecx: DWORD, + Eax: DWORD, + Ebp: DWORD, + Eip: DWORD, + SegCs: DWORD, + EFlags: DWORD, + Esp: DWORD, + SegSs: DWORD, + ExtendedRegisters: [WOW64_MAXIMUM_SUPPORTED_EXTENSION]BYTE, +} + +PWOW64_CONTEXT :: ^WOW64_CONTEXT + +WOW64_FLOATING_SAVE_AREA :: struct { + ControlWord: DWORD, + StatusWord: DWORD, + TagWord: DWORD, + ErrorOffset: DWORD, + ErrorSelector: DWORD, + DataOffset: DWORD, + DataSelector: DWORD, + RegisterArea: [WOW64_SIZE_OF_80387_REGISTERS]BYTE, + Cr0NpxState: DWORD, +} + +PWOW64_FLOATING_SAVE_AREA :: ^WOW64_FLOATING_SAVE_AREA \ No newline at end of file diff --git a/core/testing/runner.odin b/core/testing/runner.odin index fa7c2ffd2..16967e3c7 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -6,6 +6,7 @@ import "base:runtime" import "core:bytes" import "core:encoding/ansi" @require import "core:encoding/base64" +@require import "core:encoding/json" import "core:fmt" import "core:io" @require import pkg_log "core:log" @@ -44,7 +45,8 @@ SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info") // Show only the most necessary logging information. USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false) - +// Output a report of the tests to the given path. +JSON_REPORT : string : #config(ODIN_TEST_JSON_REPORT, "") get_log_level :: #force_inline proc() -> runtime.Logger_Level { when ODIN_DEBUG { @@ -61,6 +63,18 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level { } } +JSON :: struct { + total: int, + success: int, + duration: time.Duration, + packages: map[string][dynamic]JSON_Test, +} + +JSON_Test :: struct { + success: bool, + name: string, +} + end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { #no_bounds_check c := t.cleanups[i] @@ -654,8 +668,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { #no_bounds_check pkg := report.packages_by_name[it.pkg] pkg.frame_ready = false - fmt.assertf(thread.pool_stop_task(&pool, test_index), - "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", + found := thread.pool_stop_task(&pool, test_index) + fmt.assertf(found, "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", reason, test_index, it.pkg, it.name) // The order this is handled in is a little particular. @@ -847,5 +861,35 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_ fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) + when JSON_REPORT != "" { + json_report: JSON + + mode: int + when ODIN_OS != .Windows { + mode = os.S_IRUSR|os.S_IWUSR|os.S_IRGRP|os.S_IROTH + } + json_fd, err := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) + fmt.assertf(err == nil, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, err) + defer os.close(json_fd) + + for test, i in report.all_tests { + #no_bounds_check state := report.all_test_states[i] + + if test.pkg not_in json_report.packages { + json_report.packages[test.pkg] = {} + } + + tests := &json_report.packages[test.pkg] + append(tests, JSON_Test{name = test.name, success = state == .Successful}) + } + + json_report.total = len(internal_tests) + json_report.success = total_success_count + json_report.duration = finished_in + + err := json.marshal_to_writer(os.stream_from_handle(json_fd), json_report, &{ pretty = true }) + fmt.assertf(err == nil, "Error writing JSON report: %v", err) + } + return total_success_count == total_test_count } diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 80e60d6cf..17ba1a0a2 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -6,12 +6,26 @@ import "base:intrinsics" _ :: intrinsics +/* +Value, specifying whether `core:thread` functionality is available on the +current platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Type for a procedure that will be run in a thread, after that thread has been +started. +*/ Thread_Proc :: #type proc(^Thread) +/* +Maximum number of user arguments for polymorphic thread procedures. +*/ MAX_USER_ARGUMENTS :: 8 +/* +Type representing the state/flags of the thread. +*/ Thread_State :: enum u8 { Started, Joined, @@ -19,44 +33,48 @@ Thread_State :: enum u8 { Self_Cleanup, } +/* +Type representing a thread handle and the associated with that thread data. +*/ Thread :: struct { using specific: Thread_Os_Specific, flags: bit_set[Thread_State; u8], - id: int, - procedure: Thread_Proc, - - /* - These are values that the user can set as they wish, after the thread has been created. - This data is easily available to the thread proc. - - These fields can be assigned to directly. - - Should be set after the thread is created, but before it is started. - */ - data: rawptr, - user_index: int, - user_args: [MAX_USER_ARGUMENTS]rawptr, - - /* - The context to be used as 'context' in the thread proc. - - This field can be assigned to directly, after the thread has been created, but __before__ the thread has been started. - This field must not be changed after the thread has started. - - NOTE: If you __don't__ set this, the temp allocator will be managed for you; - If you __do__ set this, then you're expected to handle whatever allocators you set, yourself. - - IMPORTANT: - By default, the thread proc will get the same context as `main()` gets. - In this situation, the thread will get a new temporary allocator which will be cleaned up when the thread dies. - ***This does NOT happen when you set `init_context`.*** - This means that if you set `init_context`, but still have the `temp_allocator` field set to the default temp allocator, - then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually, - in order to prevent any memory leaks. - This call ***must*** be done ***in the thread proc*** because the default temporary allocator uses thread local state! - */ + // Thread ID. + id: int, + // The thread procedure. + procedure: Thread_Proc, + // User-supplied pointer, that will be available to the thread once it is + // started. Should be set after the thread has been created, but before + // it is started. + data: rawptr, + // User-supplied integer, that will be available to the thread once it is + // started. Should be set after the thread has been created, but before + // it is started. + user_index: int, + // User-supplied array of arguments, that will be available to the thread, + // once it is started. Should be set after the thread has been created, + // but before it is started. + user_args: [MAX_USER_ARGUMENTS]rawptr, + // The thread context. + // This field can be assigned to directly, after the thread has been + // created, but __before__ the thread has been started. This field must + // not be changed after the thread has started. + // + // **Note**: If this field is **not** set, the temp allocator will be managed + // automatically. If it is set, the allocators must be handled manually. + // + // **IMPORTANT**: + // By default, the thread proc will get the same context as `main()` gets. + // In this situation, the thread will get a new temporary allocator which + // will be cleaned up when the thread dies. ***This does NOT happen when + // `init_context` field is initialized***. + // + // If `init_context` is initialized, and `temp_allocator` field is set to + // the default temp allocator, then `runtime.default_temp_allocator_destroy()` + // procedure needs to be called from the thread procedure, in order to prevent + // any memory leaks. init_context: Maybe(runtime.Context), - + // The allocator used to allocate data for the thread. creation_allocator: mem.Allocator, } @@ -64,6 +82,9 @@ when IS_SUPPORTED { #assert(size_of(Thread{}.user_index) == size_of(uintptr)) } +/* +Type representing priority of a thread. +*/ Thread_Priority :: enum { Normal, Low, @@ -71,74 +92,178 @@ Thread_Priority :: enum { } /* - Creates a thread in a suspended state with the given priority. - To start the thread, call `thread.start()`. +Create a thread in a suspended state with the given priority. - See `thread.create_and_start()`. +This procedure creates a thread that will be set to run the procedure +specified by `procedure` parameter with a specified priority. The returned +thread will be in a suspended state, until `start()` procedure is called. + +To start the thread, call `start()`. Also the `create_and_start()` +procedure can be called to create and start the thread immediately. */ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { return _create(procedure, priority) } + +/* +Wait for the thread to finish and free all data associated with it. +*/ destroy :: proc(thread: ^Thread) { _destroy(thread) } +/* +Start a suspended thread. +*/ start :: proc(thread: ^Thread) { _start(thread) } +/* +Check if the thread has finished work. +*/ is_done :: proc(thread: ^Thread) -> bool { return _is_done(thread) } - +/* +Wait for the thread to finish work. +*/ join :: proc(thread: ^Thread) { _join(thread) } - +/* +Wait for all threads to finish work. +*/ join_multiple :: proc(threads: ..^Thread) { _join_multiple(..threads) } +/* +Forcibly terminate a running thread. +*/ terminate :: proc(thread: ^Thread, exit_code: int) { _terminate(thread, exit_code) } +/* +Yield the execution of the current thread to another OS thread or process. +*/ yield :: proc() { _yield() } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start(fn, init_context, priority, true) } +/* +Run a procedure with one pointer parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start_with_data(data, fn, init_context, priority, true) } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data(data, fn, init_context, priority, true) } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) } + +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc())t.data @@ -154,9 +279,22 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, return t } +/* +Run a procedure with one pointer parameter on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data @@ -176,6 +314,22 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co return t } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -201,6 +355,22 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex return t } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -232,6 +402,22 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), return t } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -264,6 +450,23 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr start(t) return t } + +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 363f50862..f56454bfc 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -81,9 +81,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { defer unix.pthread_attr_destroy(&attrs) // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. - assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0) + res: i32 + res = unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) + assert(res == 0) when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { - assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) + res = unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) + assert(res == 0) } thread := new(Thread) @@ -94,7 +97,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Set thread priority. policy: i32 - res: i32 when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { res = unix.pthread_attr_getschedpolicy(&attrs, &policy) assert(res == 0) diff --git a/core/time/datetime/constants.odin b/core/time/datetime/constants.odin index a2a02838c..5f336ef4a 100644 --- a/core/time/datetime/constants.odin +++ b/core/time/datetime/constants.odin @@ -1,16 +1,46 @@ package datetime -// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) -// | Midnight Monday, January 3, 1 A.D. (Julian) +/* +Type representing a mononotic day number corresponding to a date. + + Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) + | Midnight Monday, January 3, 1 A.D. (Julian) +*/ Ordinal :: i64 + +/* +*/ EPOCH :: Ordinal(1) -// Minimum and maximum dates and ordinals. Chosen for safe roundtripping. +/* +Minimum valid value for date. + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1} + +/* +Maximum valid value for date + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31} + +/* +Minimum value for an ordinal +*/ MIN_ORD :: Ordinal(-9_223_372_036_854_775_234) + +/* +Maximum value for an ordinal +*/ MAX_ORD :: Ordinal( 9_223_372_036_854_774_869) +/* +Possible errors returned by datetime functions. +*/ Error :: enum { None, Invalid_Year, @@ -24,12 +54,22 @@ Error :: enum { Invalid_Delta, } +/* +A type representing a date. + +The minimum and maximum values for a year can be found in `MIN_DATE` and +`MAX_DATE` constants. The `month` field can range from 1 to 12, and the day +ranges from 1 to however many days there are in the specified month. +*/ Date :: struct { year: i64, month: i8, day: i8, } +/* +A type representing a time within a single day within a nanosecond precision. +*/ Time :: struct { hour: i8, minute: i8, @@ -37,17 +77,30 @@ Time :: struct { nano: i32, } +/* +A type representing datetime. +*/ DateTime :: struct { using date: Date, using time: Time, } +/* +A type representing a difference between two instances of datetime. + +**Note**: All fields are i64 because we can also use it to add a number of +seconds or nanos to a moment, that are then normalized within their respective +ranges. +*/ Delta :: struct { - days: i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment, - seconds: i64, // that are then normalized within their respective ranges. + days: i64, + seconds: i64, nanos: i64, } +/* +Type representing one of the months. +*/ Month :: enum i8 { January = 1, February, @@ -63,6 +116,9 @@ Month :: enum i8 { December, } +/* +Type representing one of the weekdays. +*/ Weekday :: enum i8 { Sunday = 0, Monday, diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin index 89fa2ce98..fc9780e3b 100644 --- a/core/time/datetime/datetime.odin +++ b/core/time/datetime/datetime.odin @@ -1,56 +1,113 @@ /* - Calendrical conversions using a proleptic Gregorian calendar. +Calendrical conversions using a proleptic Gregorian calendar. - Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz +Implemented using formulas from: Calendrical Calculations Ultimate Edition, +Reingold & Dershowitz */ package datetime import "base:intrinsics" -// Procedures that return an Ordinal +/* +Obtain an ordinal from a date. +This procedure converts the specified date into an ordinal. If the specified +date is not a valid date, an error is returned. +*/ date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) { validate(date) or_return return unsafe_date_to_ordinal(date), .None } +/* +Obtain an ordinal from date components. + +This procedure converts the specified date, provided by its individual +components, into an ordinal. If the specified date is not a valid date, an error +is returned. +*/ components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) { validate(year, month, day) or_return return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None } -// Procedures that return a Date +/* +Obtain date using an Ordinal. +This provedure converts the specified ordinal into a date. If the ordinal is not +a valid ordinal, an error is returned. +*/ ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) { validate(ordinal) or_return return unsafe_ordinal_to_date(ordinal), .None } +/* +Obtain a date from date components. + +This procedure converts date components, specified by a year, a month and a day, +into a date object. If the provided date components don't represent a valid +date, an error is returned. +*/ components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) { validate(year, month, day) or_return return Date{i64(year), i8(month), i8(day)}, .None } +/* +Obtain time from time components. + +This procedure converts time components, specified by an hour, a minute, a second +and nanoseconds, into a time object. If the provided time components don't +represent a valid time, an error is returned. +*/ components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) { validate(hour, minute, second, nanos) or_return return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None } +/* +Obtain datetime from components. + +This procedure converts date components and time components into a datetime object. +If the provided date components or time components don't represent a valid +datetime, an error is returned. +*/ components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) { date := components_to_date(year, month, day) or_return time := components_to_time(hour, minute, second, nanos) or_return return {date, time}, .None } +/* +Obtain an datetime from an ordinal. + +This procedure converts the value of an ordinal into a datetime. Since the +ordinal only has the amount of days, the resulting time in the datetime +object will always have the time equal to `00:00:00.000`. +*/ ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) { d := ordinal_to_date(ordinal) or_return return {Date(d), {}}, .None } +/* +Calculate the weekday from an ordinal. + +This procedure takes the value of an ordinal and returns the day of week for +that ordinal. +*/ day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { - return Weekday((ordinal - EPOCH) %% 7) + return Weekday((ordinal - EPOCH + 1) %% 7) } +/* +Calculate the difference between two dates. + +This procedure calculates the difference between two dates `a - b`, and returns +a delta between the two dates in `days`. If either `a` or `b` is not a valid +date, an error is returned. +*/ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) { ord_a := date_to_ordinal(a) or_return ord_b := date_to_ordinal(b) or_return @@ -59,6 +116,16 @@ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) return } +/* +Calculate the difference between two datetimes. + +This procedure calculates the difference between two datetimes, `a - b`, and +returns a delta between the two dates. The difference is returned in all three +fields of the `Delta` struct: the difference in days, the difference in seconds +and the difference in nanoseconds. + +If either `a` or `b` is not a valid datetime, an error is returned. +*/ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) { ord_a := date_to_ordinal(a) or_return ord_b := date_to_ordinal(b) or_return @@ -73,19 +140,42 @@ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: return } +/* +Calculate a difference between two deltas. +*/ subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) { delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos} delta = normalize_delta(delta) or_return return } + +/* +Calculate a difference between two datetimes, dates or deltas. +*/ sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas} +/* +Add certain amount of days to a date. + +This procedure adds the specified amount of days to a date and returns a new +date. The new date would have happened the specified amount of days after the +specified date. +*/ add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) { ord := date_to_ordinal(a) or_return ord += days return ordinal_to_date(ord) } +/* +Add delta to a date. + +This procedure adds a delta to a date, and returns a new date. The new date +would have happened the time specified by `delta` after the specified date. + +**Note**: The delta is assumed to be normalized. That is, if it contains seconds +or milliseconds, regardless of the amount only the days will be added. +*/ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) { ord := date_to_ordinal(a) or_return // Because the input is a Date, we add only the days from the Delta. @@ -93,6 +183,13 @@ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, return ordinal_to_date(ord) } +/* +Add delta to datetime. + +This procedure adds a delta to a datetime, and returns a new datetime. The new +datetime would have happened the time specified by `delta` after the specified +datetime. +*/ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) { days := date_to_ordinal(a) or_return @@ -110,8 +207,18 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return return } + +/* +Add days to a date, delta to a date or delta to datetime. +*/ add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime} +/* +Obtain the day number in a year + +This procedure returns the number of the day in a year, starting from 1. If +the date is not a valid date, an error is returned. +*/ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { validate(date) or_return @@ -120,6 +227,13 @@ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { return } +/* +Obtain the remaining number of days in a year. + +This procedure returns the number of days between the specified date and +December 31 of the same year. If the date is not a valid date, an error is +returned. +*/ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) { // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year validate(date) or_return @@ -127,6 +241,12 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: return delta.days, .None } +/* +Obtain the last day of a given month on a given year. + +This procedure returns the amount of days in a specified month on a specified +date. If the specified year or month is not valid, an error is returned. +*/ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) { // Not using formula 2.27 from the book. This is far simpler and gives the same answer. @@ -140,16 +260,33 @@ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) return } +/* +Obtain the new year date of a given year. + +This procedure returns the January 1st date of the specified year. If the year +is not valid, an error is returned. +*/ new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) { validate(year, 1, 1) or_return return {year, 1, 1}, .None } +/* +Obtain the end year of a given date. + +This procedure returns the December 31st date of the specified year. If the year +is not valid, an error is returned. +*/ year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) { validate(year, 12, 31) or_return return {year, 12, 31}, .None } +/* +Obtain the range of dates for a given year. + +This procedure returns dates, for every day of a given year in a slice. +*/ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) { is_leap := is_leap_year(year) @@ -171,6 +308,15 @@ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (rang return } +/* +Normalize the delta. + +This procedure normalizes the delta in such a way that the number of seconds +is between 0 and the number of seconds in the day and nanoseconds is between +0 and 10^9. + +If the value for `days` overflows during this operation, an error is returned. +*/ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) { // Distribute nanos into seconds and remainder seconds, nanos := divmod(delta.nanos, 1e9) @@ -194,6 +340,12 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: // The following procedures don't check whether their inputs are in a valid range. // They're still exported for those who know their inputs have been validated. +/* +Obtain an ordinal from a date. + +This procedure converts a date into an ordinal. If the date is not a valid date, +the result is unspecified. +*/ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) { year_minus_one := date.year - 1 @@ -223,6 +375,12 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) return } +/* +Obtain a year and a day of the year from an ordinal. + +This procedure returns the year and the day of the year of a given ordinal. +Of the ordinal is outside of its valid range, the result is unspecified. +*/ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) { // Days after epoch d0 := ordinal - EPOCH @@ -253,6 +411,12 @@ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, d return year + 1, day_ordinal } +/* +Obtain a date from an ordinal. + +This procedure converts an ordinal into a date. If the ordinal is outside of +its valid range, the result is unspecified. +*/ unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) { year, _ := unsafe_ordinal_to_year(ordinal) diff --git a/core/time/datetime/internal.odin b/core/time/datetime/internal.odin index 45c2b99ab..e7129548e 100644 --- a/core/time/datetime/internal.odin +++ b/core/time/datetime/internal.odin @@ -1,3 +1,4 @@ +//+private package datetime // Internal helper functions for calendrical conversions diff --git a/core/time/datetime/validation.odin b/core/time/datetime/validation.odin index 87d5aa1cd..0a66833b0 100644 --- a/core/time/datetime/validation.odin +++ b/core/time/datetime/validation.odin @@ -1,14 +1,29 @@ package datetime - // Validation helpers + +/* +Check if a year is a leap year. +*/ is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +/* +Check for errors in date formation. + +This procedure validates all fields of a date, and if any of the fields is +outside of allowed range, an error is returned. +*/ validate_date :: proc "contextless" (date: Date) -> (err: Error) { return validate(date.year, date.month, date.day) } +/* +Check for errors in date formation given date components. + +This procedure checks whether a date formed by the specified year month and a +day is a valid date. If not, an error is returned. +*/ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) { if year < MIN_DATE.year || year > MAX_DATE.year { return .Invalid_Year @@ -29,6 +44,12 @@ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #a return .None } +/* +Check for errors in Ordinal + +This procedure checks if the ordinal is in a valid range for roundtrip +conversions with the dates. If not, an error is returned. +*/ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { if ordinal < MIN_ORD || ordinal > MAX_ORD { return .Invalid_Ordinal @@ -36,10 +57,22 @@ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { return } +/* +Check for errors in time formation + +This procedure checks whether time has all fields in valid ranges, and if not +an error is returned. +*/ validate_time :: proc "contextless" (time: Time) -> (err: Error) { return validate(time.hour, time.minute, time.second, time.nano) } +/* +Check for errors in time formed by its components. + +This procedure checks whether the time formed by its components is valid, and +if not an error is returned. +*/ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) { if hour < 0 || hour > 23 { return .Invalid_Hour @@ -56,12 +89,21 @@ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minut return .None } +/* +Check for errors in datetime formation. + +This procedure checks whether all fields of date and time in the specified +datetime are valid, and if not, an error is returned. +*/ validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) { validate(datetime.date) or_return validate(datetime.time) or_return return .None } +/* +Check for errors in date, time or datetime. +*/ validate :: proc{ validate_date, validate_year_month_day, diff --git a/core/time/iso8601.odin b/core/time/iso8601.odin index 528e0b00a..f00107226 100644 --- a/core/time/iso8601.odin +++ b/core/time/iso8601.odin @@ -3,23 +3,62 @@ package time import dt "core:time/datetime" -// Parses an ISO 8601 string and returns Time in UTC, with any UTC offset applied to it. -// Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an ISO 8601 string into a time with UTC offset applied to it. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string. In case the timezone offset +is specified in the string, that timezone is applied to time. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time represented by `iso_datetime`, with UTC offset applied. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { offset: int - res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) res._nsec += (i64(-offset) * i64(Minute)) return res, consumed } -// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Note: Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an ISO 8601 string into a time and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time in UTC. +- `utc_offset`: The UTC offset of the time, in minutes. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { moment, offset, leap_second, count := iso8601_to_components(iso_datetime) if count == 0 { @@ -37,9 +76,32 @@ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) - } } -// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +/* +Parse an ISO 8601 string into a datetime and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns datetime, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed + +**Returns**: +- `res`: The parsed datetime, in UTC. +- `utc_offset`: The UTC offset, in minutes. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- This procedure performs no validation on whether components are valid, + e.g. it'll return hour = 25 if that's what it's given in the specified + string. +*/ iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime) if !ok { diff --git a/core/time/perf.odin b/core/time/perf.odin index 123d67eca..784d7acd6 100644 --- a/core/time/perf.odin +++ b/core/time/perf.odin @@ -3,18 +3,39 @@ package time import "base:runtime" import "base:intrinsics" +/* +Type representing monotonic time, useful for measuring durations. +*/ Tick :: struct { _nsec: i64, // relative amount } + +/* +Obtain the current tick. +*/ tick_now :: proc "contextless" () -> Tick { return _tick_now() } +/* +Obtain the difference between ticks. +*/ tick_diff :: proc "contextless" (start, end: Tick) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Incrementally obtain durations since last tick. + +This procedure returns the duration between the current tick and the tick +stored in `prev` pointer, and then stores the current tick in location, +specified by `prev`. If the prev pointer contains an zero-initialized tick, +then the returned duration is 0. + +This procedure is meant to be used in a loop, or in other scenarios, where one +might want to obtain time between multiple ticks at specific points. +*/ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { d: Duration t := tick_now() @@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { return d } +/* +Obtain the duration since last tick. +*/ tick_since :: proc "contextless" (start: Tick) -> Duration { return tick_diff(start, tick_now()) } - +/* +Capture the duration the code in the current scope takes to execute. +*/ @(deferred_in_out=_tick_duration_end) SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick { return tick_now() } - _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) { d^ = tick_since(t) } @@ -62,6 +87,13 @@ when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD { } } +/* +Check if the CPU has invariant TSC. + +This procedure checks if the CPU contains an invariant TSC (Time stamp counter). +Invariant TSC is a feature of modern processors that allows them to run their +TSC at a fixed frequency, independent of ACPI state, and CPU frequency. +*/ has_invariant_tsc :: proc "contextless" () -> bool { when ODIN_ARCH == .amd64 { return x86_has_invariant_tsc() @@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool { return false } +/* +Obtain the CPU's TSC frequency, in hertz. + +This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU +doesn't have an invariant TSC, this procedure returns with an error. Otherwise +an attempt is made to fetch the TSC frequency from the OS. If this fails, +the frequency is obtained by sleeping for the specified amount of time and +dividing the readings from TSC by the duration of the sleep. + +The duration of sleep can be controlled by `fallback_sleep` parameter. +*/ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) { if !has_invariant_tsc() { return 0, false @@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool return hz, true } -/* - Benchmark helpers -*/ +// Benchmark helpers +/* +Errors returned by the `benchmark()` procedure. +*/ Benchmark_Error :: enum { Okay = 0, Allocation_Error, } +/* +Options for benchmarking. +*/ Benchmark_Options :: struct { + // The initialization procedure. `benchmark()` will call this before taking measurements. setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The procedure to benchmark. bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The deinitialization procedure. teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), - + // Field to be used by `bench()` procedure for any purpose. rounds: int, + // Field to be used by `bench()` procedure for any purpose. bytes: int, + // Field to be used by `bench()` procedure for any purpose. input: []u8, - + // `bench()` writes to specify the count of elements processed. count: int, + // `bench()` writes to specify the number of bytes processed. processed: int, + // `bench()` can write the output slice here. output: []u8, // Unused for hash benchmarks + // `bench()` can write the output hash here. hash: u128, - - /* - Performance - */ + // `benchmark()` procedure will output the duration of benchmark duration: Duration, + // `benchmark()` procedure will output the average count of elements + // processed per second, using the `count` field of this struct. rounds_per_second: f64, + // `benchmark()` procedure will output the average number of megabytes + // processed per second, using the `processed` field of this struct. megabytes_per_second: f64, } +/* +Benchmark a procedure. + +This procedure produces a benchmark. The procedure specified in the `bench` +field of the `options` parameter will be benchmarked. The following metrics +can be obtained: + +- Run time of the procedure +- Number of elements per second processed on average +- Number of bytes per second this processed on average + +In order to obtain these metrics, the `bench()` procedure writes to `options` +struct the number of elements or bytes it has processed. +*/ benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) { assert(options != nil) assert(options.bench != nil) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 0a2d431b7..e4c6565d6 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -4,10 +4,33 @@ package time import dt "core:time/datetime" -// Parses an RFC 3339 string and returns Time in UTC, with any UTC offset applied to it. -// Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an RFC 3339 string into time with a UTC offset applied to it. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time that was represented by the RFC 3339 string, with the UTC +offset applied to it. + +**Inputs**: +- `rfc_datetime`: An RFC 3339 string to parse. +- `is_leap`: Optional output parameter specifying whether the moment was a leap + second. + +**Returns**: +- `res`: The time, with UTC offset applied, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the RFC 3339 string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { offset: int @@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: return res, consumed } -// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Note: Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an RFC 3339 string into a time and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time, in UTC and a UTC offset, in minutes, that were represented +by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to be parsed. +- `is_leap`: Optional output parameter specifying whether the moment was a + leap second. + +**Returns**: +- `res`: The time, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime) if count == 0 { @@ -38,9 +84,31 @@ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) - } } -// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +/* +Parse an RFC 3339 string into a datetime and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the datetime, in UTC and the UTC offset, in minutes, that were +represented by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to parse. + +**Returns**: +- `res`: The datetime, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: Number of bytes consumed by parsing the string. + +Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +*/ rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime) if !ok { diff --git a/core/time/time.odin b/core/time/time.odin index 4ea5afc70..e4ec67be3 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -3,24 +3,73 @@ package time import "base:intrinsics" import dt "core:time/datetime" +/* +Type representing duration, with nanosecond precision. +This is the regular Unix timestamp, scaled to nanosecond precision. +*/ Duration :: distinct i64 +/* +The duration equal to one nanosecond (1e-9 seconds). +*/ Nanosecond :: Duration(1) + +/* +The duration equal to one microsecond (1e-6 seconds). +*/ Microsecond :: 1000 * Nanosecond + +/* +The duration equal to one millisecond (1e-3 seconds). +*/ Millisecond :: 1000 * Microsecond + +/* +The duration equal to one second. +*/ Second :: 1000 * Millisecond + +/* +The duration equal to one minute (60 seconds). +*/ Minute :: 60 * Second + +/* +The duration equal to one hour (3600 seconds). +*/ Hour :: 60 * Minute +/* +Minimum representable duration. +*/ MIN_DURATION :: Duration(-1 << 63) + +/* +Maximum representable duration. +*/ MAX_DURATION :: Duration(1<<63 - 1) +/* +Value specifying whether the time procedures are supported by the current +platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Specifies time since the UNIX epoch, with nanosecond precision. + +Capable of representing any time within the following range: + +- `min: 1677-09-21 00:12:44.145224192 +0000 UTC` +- `max: 2262-04-11 23:47:16.854775807 +0000 UTC` +*/ Time :: struct { _nsec: i64, // Measured in UNIX nanonseconds } +/* +Type representing a month. +*/ Month :: enum int { January = 1, February, @@ -36,6 +85,9 @@ Month :: enum int { December, } +/* +Type representing a weekday. +*/ Weekday :: enum int { Sunday = 0, Monday, @@ -46,20 +98,37 @@ Weekday :: enum int { Saturday, } +/* +Type representing a stopwatch. + +The stopwatch is used for measuring the total time in multiple "runs". When the +stopwatch is started, it starts counting time. When the stopwatch is stopped, +the difference in time between the last start and the stop is added to the +total. When the stopwatch resets, the total is reset. +*/ Stopwatch :: struct { running: bool, _start_time: Tick, _accumulation: Duration, } +/* +Obtain the current time. +*/ now :: proc "contextless" () -> Time { return _now() } +/* +Sleep for the specified duration. +*/ sleep :: proc "contextless" (d: Duration) { _sleep(d) } +/* +Start the stopwatch. +*/ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { if !stopwatch.running { stopwatch._start_time = tick_now() @@ -67,6 +136,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Stop the stopwatch. +*/ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { if stopwatch.running { stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now()) @@ -74,11 +146,21 @@ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Reset the stopwatch. +*/ stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch._accumulation = {} stopwatch.running = false } +/* +Obtain the total time, counted by the stopwatch. + +This procedure obtains the total time, counted by the stopwatch. If the stopwatch +isn't stopped at the time of calling this procedure, the time between the last +start and the current time is also accounted for. +*/ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { if !stopwatch.running { return stopwatch._accumulation @@ -86,40 +168,86 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now()) } +/* +Calculate the duration elapsed between two times. +*/ diff :: proc "contextless" (start, end: Time) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Calculate the duration elapsed since a specific time. +*/ since :: proc "contextless" (start: Time) -> Duration { return diff(start, now()) } +/* +Obtain the number of nanoseconds in a duration. +*/ duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } + +/* +Obtain the number of microseconds in a duration. +*/ duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } + +/* +Obtain the number of milliseconds in a duration. +*/ duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } + +/* +Obtain the number of seconds in a duration. +*/ duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } + +/* +Obtain the number of minutes in a duration. +*/ duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } + +/* +Obtain the number of hours in a duration. +*/ duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } +/* +Round a duration to a specific unit + +This procedure rounds the duration to a specific unit + +**Note**: Any duration can be supplied as a unit. + +Inputs: +- d: The duration to round +- m: The unit to round to + +Returns: +- The duration `d`, rounded to the unit specified by `m` + +Example: + time.duration_round(my_duration, time.Second) +*/ duration_round :: proc "contextless" (d, m: Duration) -> Duration { _less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool { return u64(x)+u64(x) < u64(y) @@ -149,50 +277,97 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration { return MAX_DURATION } +/* +Truncate the duration to the specified unit. + +This procedure truncates the duration `d` to the unit specified by `m`. + +**Note**: Any duration can be supplied as a unit. + +Inputs: +- d: The duration to truncate. +- m: The unit to truncate to. + +Returns: +- The duration `d`, truncated to the unit specified by `m`. + +Example: + time.duration_round(my_duration, time.Second) +*/ duration_truncate :: proc "contextless" (d, m: Duration) -> Duration { return d if m <= 0 else d - d%m } +/* +Parse time into date components. +*/ date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) { year, month, day, _ = _abs_date(_time_abs(t), true) return } +/* +Obtain the year of the date specified by time. +*/ year :: proc "contextless" (t: Time) -> (year: int) { year, _, _, _ = _date(t, true) return } +/* +Obtain the month of the date specified by time. +*/ month :: proc "contextless" (t: Time) -> (month: Month) { _, month, _, _ = _date(t, true) return } +/* +Obtain the day of the date specified by time. +*/ day :: proc "contextless" (t: Time) -> (day: int) { _, _, day, _ = _date(t, true) return } +/* +Obtain the week day of the date specified by time. +*/ weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) { abs := _time_abs(t) sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK return Weekday(int(sec) / SECONDS_PER_DAY) } +/* +Obtain the time components from a time, a duration or a stopwatch's total. +*/ clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch } +/* +Obtain the time components from a time. +*/ clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) { return clock_from_seconds(_time_abs(t)) } +/* +Obtain the time components from a duration. +*/ clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) { return clock_from_seconds(u64(d/1e9)) } +/* +Obtain the time components from a stopwatch's total. +*/ clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) { return clock_from_duration(stopwatch_duration(s)) } +/* +Obtain the time components from the number of seconds. +*/ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { sec = int(nsec % SECONDS_PER_DAY) hour = sec / SECONDS_PER_HOUR @@ -202,10 +377,317 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { return } +MIN_HMS_LEN :: 8 +MIN_HMS_12_LEN :: 11 +MIN_YYYY_DATE_LEN :: 10 +MIN_YY_DATE_LEN :: 8 + +/* +Formats a `Time` as a 24-hour `hh:mm:ss` string. + +**Does not allocate** + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by buf + +Example: + buf: [MIN_HMS_LEN]u8 + now := time.now() + fmt.println(time.to_string_hms(now, buf[:])) +*/ +time_to_string_hms :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_HMS_LEN) + h, m, s := clock(t) + + buf[7] = '0' + u8(s % 10); s /= 10 + buf[6] = '0' + u8(s) + buf[5] = ':' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m) + buf[2] = ':' + buf[1] = '0' + u8(h % 10); h /= 10 + buf[0] = '0' + u8(h) + + return string(buf[:MIN_HMS_LEN]) +} + +/* +Formats a `Duration` as a 24-hour `hh:mm:ss` string. + +**Does not allocate** + +Inputs: +- d: The Duration to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by buf + +Example: + buf: [MIN_HMS_LEN]u8 + d := time.since(earlier) + fmt.println(time.to_string_hms(now, buf[:])) +*/ +duration_to_string_hms :: proc(d: Duration, buf: []u8) -> (res: string) #no_bounds_check { + return time_to_string_hms(Time{_nsec=i64(d)}, buf) +} + +to_string_hms :: proc{time_to_string_hms, duration_to_string_hms} + +/* +Formats a `Time` as a 12-hour `hh:mm:ss pm` string + +**Does not allocate** + +Inputs: +- t: The Time to format +- buf: The backing buffer to use +- ampm: An optional pair of am/pm strings to use in place of the default + +Returns: +- res: The formatted string, backed by buf + +Example: + buf: [64]u8 + now := time.now() + fmt.println(time.to_string_hms_12(now, buf[:])) + fmt.println(time.to_string_hms_12(now, buf[:], {"㏂", "㏘"})) +*/ +to_string_hms_12 :: proc(t: Time, buf: []u8, ampm: [2]string = {" am", " pm"}) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_HMS_LEN + max(len(ampm[0]), len(ampm[1]))) + h, m, s := clock(t) + + _h := h % 12 + buf[7] = '0' + u8(s % 10); s /= 10 + buf[6] = '0' + u8(s) + buf[5] = ':' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m) + buf[2] = ':' + buf[1] = '0' + u8(_h% 10); _h /= 10 + buf[0] = '0' + u8(_h) + + if h < 13 { + copy(buf[8:], ampm[0]) + return string(buf[:MIN_HMS_LEN+len(ampm[0])]) + } else { + copy(buf[8:], ampm[1]) + return string(buf[:MIN_HMS_LEN+len(ampm[1])]) + } +} + +/* +Formats a Time as a yyyy-mm-dd date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YYYY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_yyyy_mm_dd(now, buf[:])) +*/ +to_string_yyyy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YYYY_DATE_LEN) + y, _m, d := date(t) + m := u8(_m) + + buf[9] = '0' + u8(d % 10); d /= 10 + buf[8] = '0' + u8(d % 10) + buf[7] = '-' + buf[6] = '0' + u8(m % 10); m /= 10 + buf[5] = '0' + u8(m % 10) + buf[4] = '-' + buf[3] = '0' + u8(y % 10); y /= 10 + buf[2] = '0' + u8(y % 10); y /= 10 + buf[1] = '0' + u8(y % 10); y /= 10 + buf[0] = '0' + u8(y) + + return string(buf[:MIN_YYYY_DATE_LEN]) +} + +/* +Formats a Time as a yy-mm-dd date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_yy_mm_dd(now, buf[:])) +*/ +to_string_yy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YY_DATE_LEN) + y, _m, d := date(t) + y %= 100; m := u8(_m) + + buf[7] = '0' + u8(d % 10); d /= 10 + buf[6] = '0' + u8(d % 10) + buf[5] = '-' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m % 10) + buf[2] = '-' + buf[1] = '0' + u8(y % 10); y /= 10 + buf[0] = '0' + u8(y) + + return string(buf[:MIN_YY_DATE_LEN]) +} + +/* +Formats a Time as a dd-mm-yyyy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YYYY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_dd_mm_yyyy(now, buf[:])) +*/ +to_string_dd_mm_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YYYY_DATE_LEN) + y, _m, d := date(t) + m := u8(_m) + + buf[9] = '0' + u8(y % 10); y /= 10 + buf[8] = '0' + u8(y % 10); y /= 10 + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m % 10) + buf[2] = '-' + buf[1] = '0' + u8(d % 10); d /= 10 + buf[0] = '0' + u8(d % 10) + + return string(buf[:MIN_YYYY_DATE_LEN]) +} + +/* +Formats a Time as a dd-mm-yy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_dd_mm_yy(now, buf[:])) +*/ +to_string_dd_mm_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YY_DATE_LEN) + y, _m, d := date(t) + y %= 100; m := u8(_m) + + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m % 10) + buf[2] = '-' + buf[1] = '0' + u8(d % 10); d /= 10 + buf[0] = '0' + u8(d % 10) + + return string(buf[:MIN_YY_DATE_LEN]) +} + +/* +Formats a Time as a mm-dd-yyyy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YYYY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_mm_dd_yyyy(now, buf[:])) +*/ +to_string_mm_dd_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YYYY_DATE_LEN) + y, _m, d := date(t) + m := u8(_m) + + buf[9] = '0' + u8(y % 10); y /= 10 + buf[8] = '0' + u8(y % 10); y /= 10 + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(d % 10); d /= 10 + buf[3] = '0' + u8(d % 10) + buf[2] = '-' + buf[1] = '0' + u8(m % 10); m /= 10 + buf[0] = '0' + u8(m % 10) + + return string(buf[:MIN_YYYY_DATE_LEN]) +} + +/* +Formats a Time as a mm-dd-yy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_mm_dd_yy(now, buf[:])) +*/ +to_string_mm_dd_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YY_DATE_LEN) + y, _m, d := date(t) + y %= 100; m := u8(_m) + + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(d % 10); d /= 10 + buf[3] = '0' + u8(d % 10) + buf[2] = '-' + buf[1] = '0' + u8(m % 10); m /= 10 + buf[0] = '0' + u8(m % 10) + + return string(buf[:MIN_YY_DATE_LEN]) +} + +/* +Read the timestamp counter of the CPU. +*/ read_cycle_counter :: proc "contextless" () -> u64 { return u64(intrinsics.read_cycle_counter()) } +/* +Obtain time from unix seconds and unix nanoseconds. +*/ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { sec, nsec := sec, nsec if nsec < 0 || nsec >= 1e9 { @@ -220,31 +702,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec)} } +/* +Obtain time from unix nanoseconds. +*/ from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { return Time{nsec} } +/* +Alias for `time_to_unix`. +*/ to_unix_seconds :: time_to_unix + +/* +Obtain the Unix timestamp in seconds from a Time. +*/ time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 } +/* +Alias for `time_to_unix_nano`. +*/ to_unix_nanoseconds :: time_to_unix_nano + +/* +Obtain the Unix timestamp in nanoseconds from a Time. +*/ time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { return t._nsec } +/* +Add duration to a time. +*/ time_add :: proc "contextless" (t: Time, d: Duration) -> Time { return Time{t._nsec + i64(d)} } -// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ -// -// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case. -// On Windows it depends but is comparable with regular sleep in the worst case. -// To get the same kind of accuracy as on Linux, have your program call `windows.timeBeginPeriod(1)` to -// tell Windows to use a more accurate timer for your process. -// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`. +/* +Accurate sleep + +This procedure sleeps for the duration specified by `d`, very accurately. + +**Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/) + +**Note(linux)**: The accuracy is within around 4µs (microseconds), in the worst case. + +**Note(windows)**: The accuracy depends but is comparable with regular sleep in +the worst case. To get the same kind of accuracy as on Linux, have your program +call `windows.timeBeginPeriod(1)` to tell Windows to use a more accurate timer +for your process. Additionally your program should call `windows.timeEndPeriod(1)` +once you're done with `accurate_sleep`. +*/ accurate_sleep :: proc "contextless" (d: Duration) { to_sleep, estimate, mean, m2, count: Duration @@ -362,6 +872,13 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon return } +/* +Convert datetime components into time. + +This procedure calculates the time from datetime components supplied in the +arguments to this procedure. If the datetime components don't represent a valid +datetime, the function returns `false` in the second argument. +*/ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) { this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec) if err != .None { @@ -370,6 +887,12 @@ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_in return compound_to_time(this_date) } +/* +Convert datetime into time. + +If the datetime represents a time outside of a valid range, `false` is returned +as the second return value. See `Time` for the representable range. +*/ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) { unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} delta, err := dt.sub(datetime, unix_epoch) @@ -387,12 +910,21 @@ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: return Time{_nsec=i64(nanoseconds)}, true } +/* +Convert datetime components into time. +*/ datetime_to_time :: proc{components_to_time, compound_to_time} +/* +Check if a year is a leap year. +*/ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +/* +Days before each month in a year, not counting the leap day on february 29th. +*/ @(rodata) days_before := [?]i32{ 0, @@ -410,11 +942,37 @@ days_before := [?]i32{ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, } - +/* +Number of seconds in a minute (without leap seconds). +*/ SECONDS_PER_MINUTE :: 60 + +/* +Number of seconds in an hour (without leap seconds). +*/ SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE + +/* +Number of seconds in a day (without leap seconds). +*/ SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR + +/* +Number of seconds in a week (without leap seconds). +*/ SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY + +/* +Days in 400 years, with leap days. +*/ DAYS_PER_400_YEARS :: 365*400 + 97 + +/* +Days in 100 years, with leap days. +*/ DAYS_PER_100_YEARS :: 365*100 + 24 + +/* +Days in 4 years, with leap days. +*/ DAYS_PER_4_YEARS :: 365*4 + 1 diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..27072a480 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +# Examples + +The `example` directory contains two packages: + +A [demo](examples/demo) illustrating the basics of Odin. + +It further contains [all](examples/all), which imports all [core](core) and [vendor](vendor) packages so we can conveniently run `odin check` on everything at once. + +For additional example code, see the [examples](https://github.com/odin-lang/examples) repository. diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index eefb29aa8..d92a6b8c4 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -61,6 +61,7 @@ import cbor "core:encoding/cbor" import csv "core:encoding/csv" import endian "core:encoding/endian" import hxa "core:encoding/hxa" +import ini "core:encoding/ini" import json "core:encoding/json" import varint "core:encoding/varint" import xml "core:encoding/xml" @@ -114,6 +115,7 @@ import relative "core:relative" import reflect "core:reflect" import runtime "base:runtime" import simd "core:simd" +import x86 "core:simd/x86" import slice "core:slice" import slice_heap "core:slice/heap" import sort "core:sort" @@ -192,6 +194,7 @@ _ :: base32 _ :: base64 _ :: csv _ :: hxa +_ :: ini _ :: json _ :: varint _ :: xml @@ -234,6 +237,7 @@ _ :: relative _ :: reflect _ :: runtime _ :: simd +_ :: x86 _ :: slice _ :: slice_heap _ :: sort diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 0ad9f4ab0..d66d1ceb0 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -359,7 +359,7 @@ control_flow :: proc() { if false { f, err := os.open("my_file.txt") - if err != os.ERROR_NONE { + if err != nil { // handle error } defer os.close(f) @@ -2577,7 +2577,7 @@ bit_field_type :: proc() { { // A `bit_field` is different from a struct in that you must specify the backing type. // This backing type must be an integer or a fixed-length array of integers. - // This is useful if ther eneeds to be a specific alignment or access pattern for the record. + // This is useful if there needs to be a specific alignment or access pattern for the record. Bar :: bit_field u32 {} Baz :: bit_field [4]u8 {} diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c3b4f2506..3e2d11101 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -326,7 +326,17 @@ enum SanitizerFlags : u32 { SanitizerFlag_Thread = 1u<<2, }; +struct BuildCacheData { + u64 crc; + String cache_dir; + // manifests + String files_path; + String args_path; + String env_path; + + bool copy_already_done; +}; // This stores the information for the specify architecture of this build struct BuildContext { @@ -426,6 +436,12 @@ struct BuildContext { bool linker_map_file; bool use_separate_modules; + bool module_per_file; + bool cached; + BuildCacheData build_cache_data; + + bool internal_no_inline; + bool no_threaded_checker; bool show_debug_messages; @@ -440,6 +456,8 @@ struct BuildContext { bool min_link_libs; + bool print_linker_flags; + RelocMode reloc_mode; bool disable_red_zone; @@ -860,15 +878,6 @@ gb_internal bool is_arch_x86(void) { return false; } -gb_internal bool allow_check_foreign_filepath(void) { - switch (build_context.metrics.arch) { - case TargetArch_wasm32: - case TargetArch_wasm64p32: - return false; - } - return true; -} - // TODO(bill): OS dependent versions for the BuildContext // join_path // is_dir @@ -1639,14 +1648,27 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } } - if (bc->ODIN_DEBUG && !bc->custom_optimization_level) { + if (!bc->custom_optimization_level) { // NOTE(bill): when building with `-debug` but not specifying an optimization level // default to `-o:none` to improve the debug symbol generation by default - bc->optimization_level = -1; // -o:none + if (bc->ODIN_DEBUG) { + bc->optimization_level = -1; // -o:none + } else { + bc->optimization_level = 0; // -o:minimal + } } bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); +#if defined(GB_SYSTEM_WINDOWS) + if (bc->optimization_level <= 0) { + if (!is_arch_wasm()) { + bc->use_separate_modules = true; + } + } +#endif + + // TODO: Static map calls are bugged on `amd64sysv` abi. if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) { // ENFORCE DYNAMIC MAP CALLS @@ -1798,10 +1820,12 @@ gb_internal bool init_build_paths(String init_filename) { #if defined(GB_SYSTEM_WINDOWS) if (bc->metrics.os == TargetOs_windows) { if (bc->resource_filepath.len > 0) { - bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); - bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); + bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); + if (!string_ends_with(bc->resource_filepath, str_lit(".res"))) { + bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); + bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); + bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); + } } if (bc->pdb_filepath.len > 0) { @@ -1987,15 +2011,20 @@ gb_internal bool init_build_paths(String init_filename) { } } + String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + defer (gb_free(ha, output_file.text)); + // Check if output path is a directory. if (path_is_directory(bc->build_paths[BuildPath_Output])) { - String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); - defer (gb_free(ha, output_file.text)); gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file)); return false; } - if (!write_directory(bc->build_paths[BuildPath_Output].basename)) { + gbFile output_file_test; + gbFileError output_test_err = gb_file_open_mode(&output_file_test, gbFileMode_Append | gbFileMode_Rw, (const char*)output_file.text); + defer (gb_file_close(&output_file_test)); + + if (output_test_err != 0) { String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); defer (gb_free(ha, output_file.text)); gb_printf_err("No write permissions for output path: %.*s\n", LIT(output_file)); diff --git a/src/cached.cpp b/src/cached.cpp new file mode 100644 index 000000000..4ad65ee9e --- /dev/null +++ b/src/cached.cpp @@ -0,0 +1,461 @@ +gb_internal GB_COMPARE_PROC(string_cmp) { + String const &x = *(String *)a; + String const &y = *(String *)b; + return string_compare(x, y); +} + +gb_internal bool recursively_delete_directory(wchar_t *wpath_c) { +#if defined(GB_SYSTEM_WINDOWS) + auto const is_dots_w = [](wchar_t const *str) -> bool { + if (!str) { + return false; + } + return wcscmp(str, L".") == 0 || wcscmp(str, L"..") == 0; + }; + + TEMPORARY_ALLOCATOR_GUARD(); + + wchar_t dir_path[MAX_PATH] = {}; + wchar_t filename[MAX_PATH] = {}; + wcscpy_s(dir_path, wpath_c); + wcscat_s(dir_path, L"\\*"); + + wcscpy_s(filename, wpath_c); + wcscat_s(filename, L"\\"); + + + WIN32_FIND_DATAW find_file_data = {}; + HANDLE hfind = FindFirstFileW(dir_path, &find_file_data); + if (hfind == INVALID_HANDLE_VALUE) { + return false; + } + defer (FindClose(hfind)); + + wcscpy_s(dir_path, filename); + + for (;;) { + if (FindNextFileW(hfind, &find_file_data)) { + if (is_dots_w(find_file_data.cFileName)) { + continue; + } + wcscat_s(filename, find_file_data.cFileName); + + if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (!recursively_delete_directory(filename)) { + return false; + } + RemoveDirectoryW(filename); + wcscpy_s(filename, dir_path); + } else { + if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { + _wchmod(filename, _S_IWRITE); + } + if (!DeleteFileW(filename)) { + return false; + } + wcscpy_s(filename, dir_path); + } + } else { + if (GetLastError() == ERROR_NO_MORE_FILES) { + break; + } + return false; + } + } + + + return RemoveDirectoryW(wpath_c); +#else + return false; +#endif +} + +gb_internal bool recursively_delete_directory(String const &path) { +#if defined(GB_SYSTEM_WINDOWS) + String16 wpath = string_to_string16(permanent_allocator(), path); + wchar_t *wpath_c = alloc_wstring(permanent_allocator(), wpath); + return recursively_delete_directory(wpath_c); +#else + return false; +#endif +} + +gb_internal bool try_clear_cache(void) { + return recursively_delete_directory(str_lit(".odin-cache")); +} + + +gb_internal u64 crc64_with_seed(void const *data, isize len, u64 seed) { + isize remaining; + u64 result = ~seed; + u8 const *c = cast(u8 const *)data; + for (remaining = len; remaining--; c++) { + result = (result >> 8) ^ (GB__CRC64_TABLE[(result ^ *c) & 0xff]); + } + return ~result; +} + +gb_internal bool check_if_exists_file_otherwise_create(String const &str) { + char const *str_c = alloc_cstring(permanent_allocator(), str); + if (!gb_file_exists(str_c)) { + gbFile f = {}; + gb_file_create(&f, str_c); + gb_file_close(&f); + return true; + } + return false; +} + + +gb_internal bool check_if_exists_directory_otherwise_create(String const &str) { +#if defined(GB_SYSTEM_WINDOWS) + String16 wstr = string_to_string16(permanent_allocator(), str); + wchar_t *wstr_c = alloc_wstring(permanent_allocator(), wstr); + return CreateDirectoryW(wstr_c, nullptr); +#else + char const *str_c = alloc_cstring(permanent_allocator(), str); + if (!gb_file_exists(str_c)) { + int status = mkdir(str_c, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + return status == 0; + } + return false; +#endif +} +gb_internal bool try_copy_executable_cache_internal(bool to_cache) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + + gbString cache_name = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(cache_name)); + + String cache_dir = build_context.build_cache_data.cache_dir; + + cache_name = gb_string_append_length(cache_name, cache_dir.text, cache_dir.len); + cache_name = gb_string_appendc(cache_name, "/"); + + cache_name = gb_string_appendc(cache_name, "cached-exe"); + if (selected_target_metrics) { + cache_name = gb_string_appendc(cache_name, "-"); + cache_name = gb_string_append_length(cache_name, selected_target_metrics->name.text, selected_target_metrics->name.len); + } + if (selected_subtarget) { + String st = subtarget_strings[selected_subtarget]; + cache_name = gb_string_appendc(cache_name, "-"); + cache_name = gb_string_append_length(cache_name, st.text, st.len); + } + cache_name = gb_string_appendc(cache_name, ".bin"); + + if (to_cache) { + return gb_file_copy( + alloc_cstring(temporary_allocator(), exe_name), + cache_name, + false + ); + } else { + return gb_file_copy( + cache_name, + alloc_cstring(temporary_allocator(), exe_name), + false + ); + } +} + + + +gb_internal bool try_copy_executable_to_cache(void) { + debugf("Cache: try_copy_executable_to_cache\n"); + + if (try_copy_executable_cache_internal(true)) { + build_context.build_cache_data.copy_already_done = true; + return true; + } + return false; +} + +gb_internal bool try_copy_executable_from_cache(void) { + debugf("Cache: try_copy_executable_from_cache\n"); + + if (try_copy_executable_cache_internal(false)) { + build_context.build_cache_data.copy_already_done = true; + return true; + } + return false; +} + + +#if !defined(GB_SYSTEM_WINDOWS) +extern char **environ; +#endif + +// returns false if different, true if it is the same +gb_internal bool try_cached_build(Checker *c, Array const &args) { + TEMPORARY_ALLOCATOR_GUARD(); + + Parser *p = c->parser; + + auto files = array_make(heap_allocator()); + for (AstPackage *pkg : p->packages) { + for (AstFile *f : pkg->files) { + array_add(&files, f->fullpath); + } + } + + #if defined(GB_SYSTEM_WINDOWS) + if (build_context.has_resource) { + String res_path = {}; + if (build_context.build_paths[BuildPath_RC].basename == "") { + res_path = path_to_string(permanent_allocator(), build_context.build_paths[BuildPath_RES]); + } else { + res_path = path_to_string(permanent_allocator(), build_context.build_paths[BuildPath_RC]); + } + array_add(&files, res_path); + } + #endif + + for (auto const &entry : c->info.load_file_cache) { + auto *cache = entry.value; + if (!cache || !cache->exists) { + continue; + } + array_add(&files, cache->path); + } + + array_sort(files, string_cmp); + + u64 crc = 0; + for (String const &path : files) { + crc = crc64_with_seed(path.text, path.len, crc); + } + + String base_cache_dir = build_context.build_paths[BuildPath_Output].basename; + base_cache_dir = concatenate_strings(permanent_allocator(), base_cache_dir, str_lit("/.odin-cache")); + (void)check_if_exists_directory_otherwise_create(base_cache_dir); + + gbString crc_str = gb_string_make_reserve(permanent_allocator(), 16); + crc_str = gb_string_append_fmt(crc_str, "%016llx", crc); + String cache_dir = concatenate3_strings(permanent_allocator(), base_cache_dir, str_lit("/"), make_string_c(crc_str)); + String files_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("files.manifest")); + String args_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("args.manifest")); + String env_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("env.manifest")); + + build_context.build_cache_data.cache_dir = cache_dir; + build_context.build_cache_data.files_path = files_path; + build_context.build_cache_data.args_path = args_path; + build_context.build_cache_data.env_path = env_path; + + auto envs = array_make(heap_allocator()); + defer (array_free(&envs)); + { + #if defined(GB_SYSTEM_WINDOWS) + wchar_t *strings = GetEnvironmentStringsW(); + defer (FreeEnvironmentStringsW(strings)); + + wchar_t *curr_string = strings; + while (curr_string && *curr_string) { + String16 wstr = make_string16_c(curr_string); + curr_string += wstr.len+1; + String str = string16_to_string(temporary_allocator(), wstr); + if (string_starts_with(str, str_lit("CURR_DATE_TIME="))) { + continue; + } + array_add(&envs, str); + } + #else + char **curr_env = environ; + while (curr_env && *curr_env) { + String str = make_string_c(*curr_env++); + if (string_starts_with(str, str_lit("PROMPT="))) { + continue; + } + if (string_starts_with(str, str_lit("RPROMPT="))) { + continue; + } + array_add(&envs, str); + } + #endif + } + array_sort(envs, string_cmp); + + if (check_if_exists_directory_otherwise_create(cache_dir)) { + goto write_cache; + } + + if (check_if_exists_file_otherwise_create(files_path)) { + goto write_cache; + } + if (check_if_exists_file_otherwise_create(args_path)) { + goto write_cache; + } + if (check_if_exists_file_otherwise_create(env_path)) { + goto write_cache; + } + + { + // exists already + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), files_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize file_count = 0; + + for (; it.pos < data.len; file_count++) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + isize sep = string_index_byte(line, ' '); + if (sep < 0) { + goto write_cache; + } + + String timestamp_str = substring(line, 0, sep); + String path_str = substring(line, sep+1, line.len); + + timestamp_str = string_trim_whitespace(timestamp_str); + path_str = string_trim_whitespace(path_str); + + if (file_count >= files.count) { + goto write_cache; + } + if (files[file_count] != path_str) { + goto write_cache; + } + + u64 timestamp = exact_value_to_u64(exact_value_integer_from_string(timestamp_str)); + gbFileTime last_write_time = gb_file_last_write_time(alloc_cstring(temporary_allocator(), path_str)); + if (last_write_time != timestamp) { + goto write_cache; + } + } + + if (file_count != files.count) { + goto write_cache; + } + } + { + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), args_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize args_count = 0; + + for (; it.pos < data.len; args_count++) { + String line = string_split_iterator(&it, '\n'); + line = string_trim_whitespace(line); + if (line.len == 0) { + break; + } + if (args_count >= args.count) { + goto write_cache; + } + + if (line != args[args_count]) { + goto write_cache; + } + } + } + { + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), env_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize env_count = 0; + + for (; it.pos < data.len; env_count++) { + String line = string_split_iterator(&it, '\n'); + line = string_trim_whitespace(line); + if (line.len == 0) { + break; + } + if (env_count >= envs.count) { + goto write_cache; + } + + if (line != envs[env_count]) { + goto write_cache; + } + } + } + + return try_copy_executable_from_cache(); + +write_cache:; + { + char const *path_c = alloc_cstring(temporary_allocator(), files_path); + gb_file_remove(path_c); + + debugf("Cache: updating %s\n", path_c); + + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &path : files) { + gbFileTime ft = gb_file_last_write_time(alloc_cstring(temporary_allocator(), path)); + gb_fprintf(&f, "%llu %.*s\n", cast(unsigned long long)ft, LIT(path)); + } + } + { + char const *path_c = alloc_cstring(temporary_allocator(), args_path); + gb_file_remove(path_c); + + debugf("Cache: updating %s\n", path_c); + + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &arg : args) { + String targ = string_trim_whitespace(arg); + gb_fprintf(&f, "%.*s\n", LIT(targ)); + } + } + { + char const *path_c = alloc_cstring(temporary_allocator(), env_path); + gb_file_remove(path_c); + + debugf("Cache: updating %s\n", path_c); + + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &env : envs) { + gb_fprintf(&f, "%.*s\n", LIT(env)); + } + } + + + return false; +} + diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index c1729333f..ba42f891b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1079,7 +1079,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } -gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier) { +gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier, bool use_mutex=true) { ast_node(ce, CallExpr, call); ast_node(bd, BasicDirective, ce->proc); String builtin_name = bd->name.string; @@ -1101,7 +1101,8 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String } } - MUTEX_GUARD(&c->info->load_file_mutex); + if (use_mutex) mutex_lock(&c->info->load_file_mutex); + defer (if (use_mutex) mutex_unlock(&c->info->load_file_mutex)); gbFileError file_error = gbFileError_None; String data = {}; @@ -1296,6 +1297,9 @@ gb_internal LoadDirectiveResult check_load_directive(CheckerContext *c, Operand gb_internal int file_cache_sort_cmp(void const *x, void const *y) { LoadFileCache const *a = *(LoadFileCache const **)(x); LoadFileCache const *b = *(LoadFileCache const **)(y); + if (a == b) { + return 0; + } return string_compare(a->path, b->path); } @@ -1411,9 +1415,12 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c file_caches = array_make(heap_allocator(), 0, files_to_reserve); + mutex_lock(&c->info->load_file_mutex); + defer (mutex_unlock(&c->info->load_file_mutex)); + for (FileInfo fi : list) { LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents)) { + if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents, /*use_mutex*/false)) { array_add(&file_caches, cache); } else { result = LoadDirective_Error; @@ -2304,6 +2311,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As error(o.expr, "Invalid argument to 'type_of'"); return false; } + + if (is_type_untyped(o.type)) { + gbString t = type_to_string(o.type); + error(o.expr, "'type_of' of %s cannot be determined", t); + gb_string_free(t); + return false; + } + // NOTE(bill): Prevent type cycles for procedure declarations if (c->curr_proc_sig == o.type) { gbString s = expr_to_string(o.expr); @@ -4301,6 +4316,49 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; + case BuiltinProc_add_sat: + case BuiltinProc_sub_sat: + { + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[0]); + check_expr(c, &y, ce->args[1]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (y.mode == Addressing_Invalid) { + return false; + } + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &x, y.type); + if (is_type_untyped(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a typed integer for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + if (!is_type_integer(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected an integer for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + Type *ct = core_type(x.type); + if (is_type_different_to_arch_endianness(ct)) { + GB_ASSERT(ct->kind == Type_Basic); + if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + } + + operand->mode = Addressing_Value; + operand->type = default_type(x.type); + } + break; + case BuiltinProc_sqrt: { Operand x = {}; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 883cfcba9..a1436fe03 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -155,6 +155,154 @@ gb_internal void check_init_variables(CheckerContext *ctx, Entity **lhs, isize l } } + +gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_entity) { + // NOTE(bill): The original_entity's scope may not be same scope that it was inserted into + // e.g. file entity inserted into its package scope + String original_name = original_entity->token.string; + Scope *found_scope = nullptr; + Entity *found_entity = nullptr; + scope_lookup_parent(original_entity->scope, original_name, &found_scope, &found_entity); + if (found_scope == nullptr) { + return; + } + rw_mutex_lock(&found_scope->mutex); + defer (rw_mutex_unlock(&found_scope->mutex)); + + // IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the + // original entity was still used check checked, but the checking was only + // relying on "constant" data such as the Entity.type and Entity.Constant.value + // + // Therefore two things can be done: the type can be assigned to state that it + // has been "evaluated" and the variant data can be copied across + + string_map_set(&found_scope->elements, original_name, new_entity); + + original_entity->flags |= EntityFlag_Overridden; + original_entity->type = new_entity->type; + original_entity->aliased_of = new_entity; + + original_entity->identifier.store(new_entity->identifier); + + if (original_entity->identifier.load() != nullptr && + original_entity->identifier.load()->kind == Ast_Ident) { + original_entity->identifier.load()->Ident.entity = new_entity; + } + + // IMPORTANT NOTE(bill, 2021-04-10): copy only the variants + // This is most likely NEVER required, but it does not at all hurt to keep + isize offset = cast(u8 *)&original_entity->Dummy.start - cast(u8 *)original_entity; + isize size = gb_size_of(*original_entity) - offset; + gb_memmove(cast(u8 *)original_entity, cast(u8 *)new_entity, size); +} + +gb_internal bool check_override_as_type_due_to_aliasing(CheckerContext *ctx, Entity *e, Entity *entity, Ast *init, Type *named_type) { + if (entity != nullptr && entity->kind == Entity_TypeName) { + // @TypeAliasingProblem + // NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases + // being "confused" as constants + // + // A :: B + // C :: proc "c" (^A) + // B :: struct {x: C} + // + // A gets evaluated first, and then checks B. + // B then checks C. + // C then tries to check A which is unresolved but thought to be a constant. + // Therefore within C's check, A errs as "not a type". + // + // This is because a const declaration may or may not be a type and this cannot + // be determined from a syntactical standpoint. + // This check allows the compiler to override the entity to be checked as a type. + // + // There is no problem if B is prefixed with the `#type` helper enforcing at + // both a syntax and semantic level that B must be a type. + // + // A :: #type B + // + // This approach is not fool proof and can fail in case such as: + // + // X :: type_of(x) + // X :: Foo(int).Type + // + // Since even these kind of declarations may cause weird checking cycles. + // For the time being, these are going to be treated as an unfortunate error + // until there is a proper delaying system to try declaration again if they + // have failed. + + e->kind = Entity_TypeName; + check_type_decl(ctx, e, init, named_type); + return true; + } + return false; +} + +gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d); + +gb_internal bool check_try_override_const_decl(CheckerContext *ctx, Entity *e, Entity *entity, Ast *init, Type *named_type) { + if (entity == nullptr) { + retry_proc_lit:; + init = unparen_expr(init); + if (init == nullptr) { + return false; + } + if (init->kind == Ast_TernaryWhenExpr) { + ast_node(we, TernaryWhenExpr, init); + if (we->cond == nullptr) { + return false; + } + if (we->cond->tav.value.kind != ExactValue_Bool) { + return false; + } + init = we->cond->tav.value.value_bool ? we->x : we->y; + goto retry_proc_lit; + } if (init->kind == Ast_ProcLit) { + // NOTE(bill, 2024-07-04): Override as a procedure entity because this could be within a `when` statement + e->kind = Entity_Procedure; + e->type = nullptr; + DeclInfo *d = decl_info_of_entity(e); + d->proc_lit = init; + check_proc_decl(ctx, e, d); + return true; + } + + return false; + } + switch (entity->kind) { + case Entity_TypeName: + if (check_override_as_type_due_to_aliasing(ctx, e, entity, init, named_type)) { + return true; + } + break; + case Entity_Builtin: + if (e->type != nullptr) { + return false; + } + e->kind = Entity_Builtin; + e->Builtin.id = entity->Builtin.id; + e->type = t_invalid; + return true; + } + + if (e->type != nullptr && entity->type != nullptr) { + Operand x = {}; + x.type = entity->type; + x.mode = Addressing_Variable; + if (!check_is_assignable_to(ctx, &x, e->type)) { + return false; + } + } + + // NOTE(bill): Override aliased entity + switch (entity->kind) { + case Entity_ProcGroup: + case Entity_Procedure: + override_entity_in_scope(e, entity); + return true; + } + return false; +} + gb_internal void check_init_constant(CheckerContext *ctx, Entity *e, Operand *operand) { if (operand->mode == Addressing_Invalid || operand->type == t_invalid || @@ -165,6 +313,13 @@ gb_internal void check_init_constant(CheckerContext *ctx, Entity *e, Operand *op return; } + if (operand->mode != Addressing_Constant) { + Entity *entity = entity_of_node(operand->expr); + if (check_try_override_const_decl(ctx, e, entity, operand->expr, nullptr)) { + return; + } + } + if (operand->mode != Addressing_Constant) { gbString str = expr_to_string(operand->expr); error(operand->expr, "'%s' is not a compile-time known constant", str); @@ -373,49 +528,6 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, } -gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_entity) { - // NOTE(bill): The original_entity's scope may not be same scope that it was inserted into - // e.g. file entity inserted into its package scope - String original_name = original_entity->token.string; - Scope *found_scope = nullptr; - Entity *found_entity = nullptr; - scope_lookup_parent(original_entity->scope, original_name, &found_scope, &found_entity); - if (found_scope == nullptr) { - return; - } - rw_mutex_lock(&found_scope->mutex); - defer (rw_mutex_unlock(&found_scope->mutex)); - - // IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the - // original entity was still used check checked, but the checking was only - // relying on "constant" data such as the Entity.type and Entity.Constant.value - // - // Therefore two things can be done: the type can be assigned to state that it - // has been "evaluated" and the variant data can be copied across - - string_map_set(&found_scope->elements, original_name, new_entity); - - original_entity->flags |= EntityFlag_Overridden; - original_entity->type = new_entity->type; - original_entity->aliased_of = new_entity; - - Ast *empty_ident = nullptr; - original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier); - - if (original_entity->identifier.load() != nullptr && - original_entity->identifier.load()->kind == Ast_Ident) { - original_entity->identifier.load()->Ident.entity = new_entity; - } - - // IMPORTANT NOTE(bill, 2021-04-10): copy only the variants - // This is most likely NEVER required, but it does not at all hurt to keep - isize offset = cast(u8 *)&original_entity->Dummy.start - cast(u8 *)original_entity; - isize size = gb_size_of(*original_entity) - offset; - gb_memmove(cast(u8 *)original_entity, cast(u8 *)new_entity, size); -} - - - gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, Type *named_type) { GB_ASSERT(e->type == nullptr); GB_ASSERT(e->kind == Entity_Constant); @@ -441,41 +553,7 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr if (init != nullptr) { Entity *entity = check_entity_from_ident_or_selector(ctx, init, false); - if (entity != nullptr && entity->kind == Entity_TypeName) { - // @TypeAliasingProblem - // NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases - // being "confused" as constants - // - // A :: B - // C :: proc "c" (^A) - // B :: struct {x: C} - // - // A gets evaluated first, and then checks B. - // B then checks C. - // C then tries to check A which is unresolved but thought to be a constant. - // Therefore within C's check, A errs as "not a type". - // - // This is because a const declaration may or may not be a type and this cannot - // be determined from a syntactical standpoint. - // This check allows the compiler to override the entity to be checked as a type. - // - // There is no problem if B is prefixed with the `#type` helper enforcing at - // both a syntax and semantic level that B must be a type. - // - // A :: #type B - // - // This approach is not fool proof and can fail in case such as: - // - // X :: type_of(x) - // X :: Foo(int).Type - // - // Since even these kind of declarations may cause weird checking cycles. - // For the time being, these are going to be treated as an unfortunate error - // until there is a proper delaying system to try declaration again if they - // have failed. - - e->kind = Entity_TypeName; - check_type_decl(ctx, e, init, named_type); + if (check_override_as_type_due_to_aliasing(ctx, e, entity, init, named_type)) { return; } entity = nullptr; @@ -951,7 +1029,6 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { switch (e->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: - case ProcedureOptimizationMode_Minimal: if (pl->inlining == ProcInlining_inline) { error(e->token, "#force_inline cannot be used in conjunction with the attribute 'optimization_mode' with neither \"none\" nor \"minimal\""); } @@ -1178,9 +1255,12 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (foreign_library->LibraryName.paths.count >= 1) { module_name = foreign_library->LibraryName.paths[0]; } - name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + + if (!string_ends_with(module_name, str_lit(".o"))) { + name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + } } - + e->Procedure.is_foreign = true; e->Procedure.link_name = name; @@ -1788,5 +1868,14 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de add_deps_from_child_to_parent(decl); + for (VariadicReuseData const &vr : decl->variadic_reuses) { + GB_ASSERT(vr.slice_type->kind == Type_Slice); + Type *elem = vr.slice_type->Slice.elem; + i64 size = type_size_of(elem); + i64 align = type_align_of(elem); + decl->variadic_reuse_max_bytes = gb_max(decl->variadic_reuse_max_bytes, size*vr.max_count); + decl->variadic_reuse_max_align = gb_max(decl->variadic_reuse_max_align, align); + } + return true; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ece7f4a8e..855f1f183 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -127,6 +127,8 @@ gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finis gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y); +gb_internal bool is_exact_value_zero(ExactValue const &v); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -500,7 +502,9 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E nctx.no_polymorphic_errors = false; // NOTE(bill): Reset scope from the failed procedure type - scope_reset(scope); + scope->head_child.store(nullptr, std::memory_order_relaxed); + string_map_clear(&scope->elements); + ptr_set_clear(&scope->imported); // LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation Ast *cloned_proc_type_node = clone_ast(pt->node); @@ -548,13 +552,15 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E final_proc_type->Proc.is_poly_specialized = true; final_proc_type->Proc.is_polymorphic = true; - final_proc_type->Proc.variadic = src->Proc.variadic; - final_proc_type->Proc.require_results = src->Proc.require_results; - final_proc_type->Proc.c_vararg = src->Proc.c_vararg; - final_proc_type->Proc.has_named_results = src->Proc.has_named_results; - final_proc_type->Proc.diverging = src->Proc.diverging; - final_proc_type->Proc.return_by_pointer = src->Proc.return_by_pointer; - final_proc_type->Proc.optional_ok = src->Proc.optional_ok; + final_proc_type->Proc.variadic = src->Proc.variadic; + final_proc_type->Proc.require_results = src->Proc.require_results; + final_proc_type->Proc.c_vararg = src->Proc.c_vararg; + final_proc_type->Proc.has_named_results = src->Proc.has_named_results; + final_proc_type->Proc.diverging = src->Proc.diverging; + final_proc_type->Proc.return_by_pointer = src->Proc.return_by_pointer; + final_proc_type->Proc.optional_ok = src->Proc.optional_ok; + final_proc_type->Proc.enable_target_feature = src->Proc.enable_target_feature; + final_proc_type->Proc.require_target_feature = src->Proc.require_target_feature; for (isize i = 0; i < operands.count; i++) { @@ -587,6 +593,16 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E entity->file = base_entity->file; entity->pkg = base_entity->pkg; entity->flags = 0; + + entity->Procedure.optimization_mode = base_entity->Procedure.optimization_mode; + + if (base_entity->flags & EntityFlag_Cold) { + entity->flags |= EntityFlag_Cold; + } + if (base_entity->flags & EntityFlag_Disabled) { + entity->flags |= EntityFlag_Disabled; + } + d->entity = entity; AstFile *file = nullptr; @@ -4443,6 +4459,27 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar case Type_Union: + // IMPORTANT NOTE HACK(bill): This is just to allow for comparisons against `0` with the `os.Error` type + // as a kind of transition period + if (!build_context.strict_style && + operand->mode == Addressing_Constant && + target_type->kind == Type_Named && + (c->pkg == nullptr || c->pkg->name != "os") && + target_type->Named.name == "Error") { + Entity *e = target_type->Named.type_name; + if (e->pkg && e->pkg->name == "os") { + if (is_exact_value_zero(operand->value) && + (operand->value.kind == ExactValue_Integer || + operand->value.kind == ExactValue_Float)) { + operand->mode = Addressing_Value; + target_type = t_untyped_nil; + operand->value = empty_exact_value; + update_untyped_expr_value(c, operand->expr, operand->value); + break; + } + } + } + // "fallthrough" if (!is_operand_nil(*operand) && !is_operand_uninit(*operand)) { TEMPORARY_ALLOCATOR_GUARD(); @@ -4968,7 +5005,27 @@ gb_internal bool is_entity_declared_for_selector(Entity *entity, Scope *import_s // NOTE(bill, 2022-02-03): see `check_const_decl` for why it exists reasoning gb_internal Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast *node, bool ident_only) { - if (node->kind == Ast_Ident) { + if (node == nullptr) { + return nullptr; + } + /*if (node->kind == Ast_TernaryWhenExpr) { + ast_node(we, TernaryWhenExpr, node); + if (we->cond == nullptr) { + return nullptr; + } + if (we->cond->tav.mode != Addressing_Constant) { + return nullptr; + } + if (we->cond->tav.value.kind != ExactValue_Bool) { + return nullptr; + } + if (we->cond->tav.value.value_bool) { + return check_entity_from_ident_or_selector(c, we->x, ident_only); + } else { + Entity *e = check_entity_from_ident_or_selector(c, we->y, ident_only); + return e; + } + } else */if (node->kind == Ast_Ident) { String name = node->Ident.token.string; return scope_lookup(c->scope, name); } else if (!ident_only) if (node->kind == Ast_SelectorExpr) { @@ -6001,6 +6058,22 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; o.type = vt->type; + + // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameters with the backing array + if (c->decl) { + bool found = false; + for (auto &vr : c->decl->variadic_reuses) { + if (are_types_identical(vt->type, vr.slice_type)) { + vr.max_count = gb_max(vr.max_count, variadic_operands.count); + found = true; + break; + } + } + if (!found) { + array_add(&c->decl->variadic_reuses, VariadicReuseData{vt->type, variadic_operands.count}); + } + } + } else { dummy_argument_count += 1; o.type = t_untyped_nil; @@ -7165,6 +7238,8 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O Array operands = {}; defer (array_free(&operands)); + CallArgumentError err = CallArgumentError_None; + bool named_fields = false; { // NOTE(bill, 2019-10-26): Allow a cycle in the parameters but not in the fields themselves @@ -7182,6 +7257,11 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O Ast *arg = ce->args[i]; ast_node(fv, FieldValue, arg); + if (fv->value == nullptr) { + error(fv->eq, "Expected a value"); + err = CallArgumentError_InvalidFieldValue; + continue; + } if (fv->field->kind == Ast_Ident) { String name = fv->field->Ident.token.string; isize index = lookup_polymorphic_record_parameter(original_type, name); @@ -7220,7 +7300,10 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } - CallArgumentError err = CallArgumentError_None; + if (err != 0) { + operand->mode = Addressing_Invalid; + return err; + } TypeTuple *tuple = get_record_polymorphic_params(original_type); isize param_count = tuple->variables.count; @@ -7848,12 +7931,15 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c // NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features. if (is_call_inlined) { - GB_ASSERT(c->curr_proc_decl); - GB_ASSERT(c->curr_proc_decl->entity); - GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); - String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; - if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { - error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); + if (c->curr_proc_decl == nullptr) { + error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope"); + } else { + GB_ASSERT(c->curr_proc_decl->entity); + GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); + String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; + if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { + error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); + } } } } @@ -9886,10 +9972,14 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } Type *et = base_type(t->BitSet.elem); isize field_count = 0; - if (et->kind == Type_Enum) { + if (et != nullptr && et->kind == Type_Enum) { field_count = et->Enum.fields.count; } + if (is_type_array(bit_set_to_int(t))) { + is_constant = false; + } + if (cl->elems[0]->kind == Ast_FieldValue) { error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed"); is_constant = false; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index edf2fae39..76b6d3f40 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1060,6 +1060,9 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags if (ss->tag != nullptr) { check_expr(ctx, &x, ss->tag); check_assignment(ctx, &x, nullptr, str_lit("switch expression")); + if (x.type == nullptr) { + return; + } } else { x.mode = Addressing_Constant; x.type = t_bool; @@ -1834,7 +1837,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (rs->vals.count == 1) { Type *t = type_deref(operand.type); - if (is_type_map(t) || is_type_bit_set(t)) { + if (t != NULL && (is_type_map(t) || is_type_bit_set(t))) { gbString v = expr_to_string(rs->vals[0]); defer (gb_string_free(v)); error_line("\tSuggestion: place parentheses around the expression\n"); @@ -2031,6 +2034,12 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f gb_string_free(str); init_type = t_invalid; } + if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) { + Entity *e = entities[0]; + if (e != nullptr && e->token.string == "default") { + warning(e->token, "Did you mean 'case:'?"); + } + } } @@ -2508,7 +2517,7 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { Entity *e = entity_of_node(x); if (is_entity_local_variable(e)) { unsafe_return_error(o, "the address of a local variable"); - } else if(x->kind == Ast_CompoundLit) { + } else if (x->kind == Ast_CompoundLit) { unsafe_return_error(o, "the address of a compound literal"); } else if (x->kind == Ast_IndexExpr) { Entity *f = entity_of_node(x->IndexExpr.expr); @@ -2523,6 +2532,14 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { unsafe_return_error(o, "the address of an indexed variable", f->type); } } + } else if (expr->kind == Ast_SliceExpr) { + Ast *x = unparen_expr(expr->SliceExpr.expr); + Entity *e = entity_of_node(x); + if (is_entity_local_variable(e) && is_type_array(e->type)) { + unsafe_return_error(o, "a slice of a local variable"); + } else if (x->kind == Ast_CompoundLit) { + unsafe_return_error(o, "a slice of a compound literal"); + } } else if (o.mode == Addressing_Constant && is_type_slice(o.type)) { ERROR_BLOCK(); unsafe_return_error(o, "a compound literal of a slice"); diff --git a/src/check_type.cpp b/src/check_type.cpp index 7fd9b379a..428fe8451 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -740,7 +740,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no gb_string_free(str); } else { for_array(j, variants) { - if (are_types_identical(t, variants[j])) { + if (union_variant_index_types_equal(t, variants[j])) { ok = false; ERROR_BLOCK(); gbString str = type_to_string(t); @@ -939,22 +939,6 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam enum_type->Enum.max_value_index = max_value_index; } -gb_internal bool is_valid_bit_field_backing_type(Type *type) { - if (type == nullptr) { - return false; - } - type = base_type(type); - if (is_type_untyped(type)) { - return false; - } - if (is_type_integer(type)) { - return true; - } - if (type->kind == Type_Array) { - return is_type_integer(type->Array.elem); - } - return false; -} gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) { ast_node(bf, BitFieldType, node); @@ -1120,6 +1104,8 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, // NOTE(bill): it doesn't matter, and when it does, // that api is absolutely stupid return Endian_Unknown; + } else if (type_size_of(type) < 2) { + return Endian_Unknown; } else if (is_type_endian_specific(type)) { if (is_type_endian_little(type)) { return Endian_Little; @@ -1266,11 +1252,14 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t Type *t = default_type(lhs.type); if (bs->underlying != nullptr) { Type *u = check_type(c, bs->underlying); + // if (!is_valid_bit_field_backing_type(u)) { if (!is_type_integer(u)) { gbString ts = type_to_string(u); error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts); gb_string_free(ts); - return; + if (!is_valid_bit_field_backing_type(u)) { + return; + } } type->BitSet.underlying = u; } @@ -1570,11 +1559,30 @@ gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *pol return poly_type; } if (show_error) { + ERROR_BLOCK(); gbString pts = type_to_string(poly_type); gbString ots = type_to_string(operand.type, true); defer (gb_string_free(pts)); defer (gb_string_free(ots)); error(operand.expr, "Cannot determine polymorphic type from parameter: '%s' to '%s'", ots, pts); + + Type *pt = poly_type; + while (pt && pt->kind == Type_Generic && pt->Generic.specialized) { + pt = pt->Generic.specialized; + } + if (is_type_slice(pt) && + (is_type_dynamic_array(operand.type) || is_type_array(operand.type))) { + Ast *expr = unparen_expr(operand.expr); + if (expr->kind == Ast_CompoundLit) { + gbString es = type_to_string(base_any_array_type(operand.type)); + error_line("\tSuggestion: Try using a slice compound literal instead '[]%s{...}'\n", es); + gb_string_free(es); + } else { + gbString os = expr_to_string(operand.expr); + error_line("\tSuggestion: Try slicing the value with '%s[:]'\n", os); + gb_string_free(os); + } + } } return t_invalid; } @@ -1763,6 +1771,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (type_expr == nullptr) { param_value = handle_parameter_value(ctx, nullptr, &type, default_value, true); } else { + Ast *original_type_expr = type_expr; if (type_expr->kind == Ast_Ellipsis) { type_expr = type_expr->Ellipsis.expr; is_variadic = true; @@ -1771,6 +1780,9 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(param, "Invalid AST: Invalid variadic parameter with multiple names"); success = false; } + + GB_ASSERT(original_type_expr->kind == Ast_Ellipsis); + type_expr = ast_array_type(type_expr->file(), original_type_expr->Ellipsis.token, nullptr, type_expr); } if (type_expr->kind == Ast_TypeidType) { ast_node(tt, TypeidType, type_expr); @@ -1794,6 +1806,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (operands != nullptr) { ctx->allow_polymorphic_types = true; } + type = check_type(ctx, type_expr); ctx->allow_polymorphic_types = prev; @@ -1826,12 +1839,12 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } type = t_invalid; } - if (is_type_empty_union(type)) { - gbString str = type_to_string(type); - error(param, "Invalid use of an empty union '%s'", str); - gb_string_free(str); - type = t_invalid; - } + // if (is_type_empty_union(type)) { + // gbString str = type_to_string(type); + // error(param, "Invalid use of an empty union '%s'", str); + // gb_string_free(str); + // type = t_invalid; + // } if (is_type_polymorphic(type)) { switch (param_value.kind) { @@ -1946,6 +1959,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; } + if (p->flags&FieldFlag_no_capture) { + error(name, "'#no_capture' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_capture; + } param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved); param->TypeName.is_type_alias = true; @@ -2047,6 +2064,28 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para p->flags &= ~FieldFlag_by_ptr; // Remove the flag } } + if (p->flags&FieldFlag_no_capture) { + if (is_variadic && variadic_index == variables.count) { + if (p->flags & FieldFlag_c_vararg) { + error(name, "'#no_capture' cannot be applied to a #c_vararg parameter"); + p->flags &= ~FieldFlag_no_capture; + } else { + error(name, "'#no_capture' is already implied on all variadic parameter"); + } + } else if (is_type_polymorphic(type)) { + // ignore + } else { + if (is_type_internally_pointer_like(type)) { + error(name, "'#no_capture' is currently reserved for future use"); + } else { + ERROR_BLOCK(); + error(name, "'#no_capture' can only be applied to pointer-like types"); + error_line("\t'#no_capture' does not currently do anything useful\n"); + p->flags &= ~FieldFlag_no_capture; + } + } + } + if (is_poly_name) { if (p->flags&FieldFlag_no_alias) { @@ -2065,6 +2104,11 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; } + if (p->flags&FieldFlag_no_capture) { + error(name, "'#no_capture' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_capture; + } + if (!is_type_polymorphic(type) && check_constant_parameter_value(type, params[i])) { // failed @@ -2079,6 +2123,16 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para param->Variable.type_expr = type_expr; } } + + if (is_variadic && variadic_index == variables.count) { + param->flags |= EntityFlag_Ellipsis; + if (is_c_vararg) { + param->flags |= EntityFlag_CVarArg; + } else { + param->flags |= EntityFlag_NoCapture; + } + } + if (p->flags&FieldFlag_no_alias) { param->flags |= EntityFlag_NoAlias; } @@ -2100,6 +2154,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (p->flags&FieldFlag_by_ptr) { param->flags |= EntityFlag_ByPtr; } + if (p->flags&FieldFlag_no_capture) { + param->flags |= EntityFlag_NoCapture; + } + param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it add_entity(ctx, scope, name, param); @@ -2113,18 +2171,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (is_variadic) { GB_ASSERT(variadic_index >= 0); - } - - if (is_variadic) { GB_ASSERT(params.count > 0); - // NOTE(bill): Change last variadic parameter to be a slice - // Custom Calling convention for variadic parameters - Entity *end = variables[variadic_index]; - end->type = alloc_type_slice(end->type); - end->flags |= EntityFlag_Ellipsis; - if (is_c_vararg) { - end->flags |= EntityFlag_CVarArg; - } } isize specialization_count = 0; @@ -2426,9 +2473,15 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) { if (e == nullptr) { return 0; } - if (e->kind == Ast_UnaryExpr && - e->UnaryExpr.op.kind == Token_Question) { - return -1; + if (e->kind == Ast_UnaryExpr) { + Token op = e->UnaryExpr.op; + if (op.kind == Token_Question) { + return -1; + } + if (e->UnaryExpr.expr == nullptr) { + error(op, "Invalid array count '[%.*s]'", LIT(op.string)); + return 0; + } } check_expr_or_type(ctx, o, e); @@ -3333,6 +3386,10 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T gbString s = expr_to_string(pt->type); error(e, "^ is used for pointer types, did you mean '&%s'?", s); gb_string_free(s); + } else if (is_type_pointer(o.type)) { + gbString s = expr_to_string(pt->type); + error(e, "^ is used for pointer types, did you mean a dereference: '%s^'?", s); + gb_string_free(s); } else { // NOTE(bill): call check_type_expr again to get a consistent error message elem = check_type_expr(&c, pt->type, nullptr); diff --git a/src/checker.cpp b/src/checker.cpp index 734659510..3eae271a0 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -50,15 +50,6 @@ gb_internal bool check_rtti_type_disallowed(Ast *expr, Type *type, char const *f return check_rtti_type_disallowed(ast_token(expr), type, format); } -gb_internal void scope_reset(Scope *scope) { - if (scope == nullptr) return; - - rw_mutex_lock(&scope->mutex); - scope->head_child.store(nullptr, std::memory_order_relaxed); - string_map_clear(&scope->elements); - ptr_set_clear(&scope->imported); - rw_mutex_unlock(&scope->mutex); -} gb_internal void scope_reserve(Scope *scope, isize count) { string_map_reserve(&scope->elements, 2*count); @@ -168,9 +159,6 @@ gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j } - - - gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { gb_zero_item(d); if (parent) { @@ -184,6 +172,9 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { ptr_set_init(&d->deps, 0); ptr_set_init(&d->type_info_deps, 0); d->labels.allocator = heap_allocator(); + d->variadic_reuses.allocator = heap_allocator(); + d->variadic_reuse_max_bytes = 0; + d->variadic_reuse_max_align = 1; } gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) { @@ -381,6 +372,7 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) { return nullptr; } + gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) { if (scope != nullptr) { bool gone_thru_proc = false; @@ -508,9 +500,15 @@ end:; return result; } +gb_global bool in_single_threaded_checker_stage = false; + gb_internal Entity *scope_insert(Scope *s, Entity *entity) { String name = entity->token.string; - return scope_insert_with_name(s, name, entity); + if (in_single_threaded_checker_stage) { + return scope_insert_with_name_no_mutex(s, name, entity); + } else { + return scope_insert_with_name(s, name, entity); + } } gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) { @@ -519,7 +517,7 @@ gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) { } -GB_COMPARE_PROC(entity_variable_pos_cmp) { +gb_internal GB_COMPARE_PROC(entity_variable_pos_cmp) { Entity *x = *cast(Entity **)a; Entity *y = *cast(Entity **)b; @@ -655,7 +653,7 @@ gb_internal bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) { } } - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Shadowed; ve->entity = e; ve->other = shadowed; @@ -674,7 +672,7 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) { } case Entity_ImportName: case Entity_LibraryName: - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Unused; ve->entity = e; return true; @@ -847,6 +845,10 @@ gb_internal void add_declaration_dependency(CheckerContext *c, Entity *e) { if (e == nullptr) { return; } + if (e->flags & EntityFlag_Disabled) { + // ignore the dependencies if it has been `@(disabled=true)` + return; + } if (c->decl != nullptr) { add_dependency(c->info, c->decl, e); } @@ -1110,7 +1112,11 @@ gb_internal void init_universal(void) { int minimum_os_version = 0; if (build_context.minimum_os_version_string != "") { int major, minor, revision = 0; + #if defined(GB_SYSTEM_WINDOWS) + sscanf_s(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + #else sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + #endif minimum_os_version = (major*10000)+(minor*100)+revision; } add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version)); @@ -1382,7 +1388,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp auto type_path = ctx->type_path; array_clear(type_path); - zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); + gb_zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); ctx->file = nullptr; ctx->scope = builtin_pkg->scope; @@ -1466,6 +1472,7 @@ gb_internal Entity *implicit_entity_of_node(Ast *clause) { } gb_internal Entity *entity_of_node(Ast *expr) { +retry:; expr = unparen_expr(expr); switch (expr->kind) { case_ast_node(ident, Ident, expr); @@ -1486,6 +1493,17 @@ gb_internal Entity *entity_of_node(Ast *expr) { case_ast_node(ce, CallExpr, expr); return ce->entity_procedure_of; case_end; + + case_ast_node(we, TernaryWhenExpr, expr); + if (we->cond == nullptr) { + break; + } + if (we->cond->tav.value.kind != ExactValue_Bool) { + break; + } + expr = we->cond->tav.value.value_bool ? we->x : we->y; + goto retry; + case_end; } return nullptr; } @@ -1772,8 +1790,7 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti if (identifier == nullptr || identifier->kind != Ast_Ident) { return; } - Ast *empty_ident = nullptr; - entity->identifier.compare_exchange_strong(empty_ident, identifier); + entity->identifier.store(identifier); identifier->Ident.entity = entity; @@ -3528,19 +3545,19 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { String mode = ev.value_string; if (mode == "none") { ac->optimization_mode = ProcedureOptimizationMode_None; + } else if (mode == "favor_size") { + ac->optimization_mode = ProcedureOptimizationMode_FavorSize; } else if (mode == "minimal") { - ac->optimization_mode = ProcedureOptimizationMode_Minimal; + error(elem, "Invalid optimization_mode 'minimal' for '%.*s', mode has been removed due to confusion, but 'none' has the same behaviour", LIT(name)); } else if (mode == "size") { - ac->optimization_mode = ProcedureOptimizationMode_Size; + error(elem, "Invalid optimization_mode 'size' for '%.*s', mode has been removed due to confusion, but 'favor_size' has the same behaviour", LIT(name)); } else if (mode == "speed") { - ac->optimization_mode = ProcedureOptimizationMode_Speed; + error(elem, "Invalid optimization_mode 'speed' for '%.*s', mode has been removed due to confusion, but 'favor_size' has the same behaviour", LIT(name)); } else { ERROR_BLOCK(); error(elem, "Invalid optimization_mode for '%.*s'. Valid modes:", LIT(name)); error_line("\tnone\n"); - error_line("\tminimal\n"); - error_line("\tsize\n"); - error_line("\tspeed\n"); + error_line("\tfavor_size\n"); } } else { error(elem, "Expected a string for '%.*s'", LIT(name)); @@ -4568,6 +4585,8 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) } gb_internal void check_all_global_entities(Checker *c) { + in_single_threaded_checker_stage = true; + // NOTE(bill): This must be single threaded // Don't bother trying for_array(i, c->info.entities) { @@ -4587,6 +4606,8 @@ gb_internal void check_all_global_entities(Checker *c) { (void)type_align_of(e->type); } } + + in_single_threaded_checker_stage = false; } @@ -5000,9 +5021,8 @@ gb_internal void check_foreign_import_fullpaths(Checker *c) { String file_str = op.value.value_string; file_str = string_trim_whitespace(file_str); - String fullpath = file_str; - if (allow_check_foreign_filepath()) { + if (!is_arch_wasm() || string_ends_with(file_str, str_lit(".o"))) { String foreign_path = {}; bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true); if (ok) { diff --git a/src/checker.hpp b/src/checker.hpp index 781737140..d76e4c7d0 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -181,6 +181,11 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] { "Checked", }; +struct VariadicReuseData { + Type *slice_type; // ..elem_type + i64 max_count; +}; + // DeclInfo is used to store information of certain declarations to allow for "any order" usage struct DeclInfo { DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment @@ -219,6 +224,10 @@ struct DeclInfo { Array labels; + Array variadic_reuses; + i64 variadic_reuse_max_bytes; + i64 variadic_reuse_max_align; + // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time struct lbModule *code_gen_module; }; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index a90c52e61..3a2e1ce22 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -70,6 +70,9 @@ enum BuiltinProcId { BuiltinProc_overflow_sub, BuiltinProc_overflow_mul, + BuiltinProc_add_sat, + BuiltinProc_sub_sat, + BuiltinProc_sqrt, BuiltinProc_fused_mul_add, @@ -393,6 +396,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("overflow_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fused_mul_add"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/common.cpp b/src/common.cpp index 69426e2a6..0ef39bd10 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -14,6 +14,8 @@ #undef NOMINMAX #endif +#include + #define GB_WINDOWS_H_INCLUDED #define GB_IMPLEMENTATION #include "gb/gb.h" diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 60e570eee..47b2796a9 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -2,13 +2,6 @@ #include #endif -gb_internal gb_inline void zero_size(void *ptr, isize len) { - memset(ptr, 0, len); -} - -#define zero_item(ptr) zero_size((ptr), gb_size_of(ptr)) - - template gb_internal gb_inline U bit_cast(V &v) { return reinterpret_cast(v); } @@ -39,6 +32,8 @@ gb_internal void virtual_memory_init(void) { } +gb_internal Thread *get_current_thread(void); + struct MemoryBlock { MemoryBlock *prev; @@ -50,8 +45,9 @@ struct MemoryBlock { struct Arena { MemoryBlock * curr_block; isize minimum_block_size; - BlockingMutex mutex; + // BlockingMutex mutex; isize temp_count; + Thread * parent_thread; }; enum { DEFAULT_MINIMUM_BLOCK_SIZE = 8ll*1024ll*1024ll }; @@ -73,10 +69,20 @@ gb_internal isize arena_align_forward_offset(Arena *arena, isize alignment) { return alignment_offset; } +gb_internal void thread_init_arenas(Thread *t) { + t->permanent_arena = gb_alloc_item(heap_allocator(), Arena); + t->temporary_arena = gb_alloc_item(heap_allocator(), Arena); + + t->permanent_arena->parent_thread = t; + t->temporary_arena->parent_thread = t; + + t->permanent_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; + t->temporary_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; +} + gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { GB_ASSERT(gb_is_power_of_two(alignment)); - - mutex_lock(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); isize size = 0; if (arena->curr_block != nullptr) { @@ -102,9 +108,7 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { curr_block->used += size; GB_ASSERT(curr_block->used <= curr_block->size); - - mutex_unlock(&arena->mutex); - + // NOTE(bill): memory will be zeroed by default due to virtual memory return ptr; } @@ -259,7 +263,7 @@ struct ArenaTemp { ArenaTemp arena_temp_begin(Arena *arena) { GB_ASSERT(arena); - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); ArenaTemp temp = {}; temp.arena = arena; @@ -274,7 +278,7 @@ ArenaTemp arena_temp_begin(Arena *arena) { void arena_temp_end(ArenaTemp const &temp) { GB_ASSERT(temp.arena); Arena *arena = temp.arena; - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); if (temp.block) { bool memory_block_found = false; @@ -310,7 +314,7 @@ void arena_temp_end(ArenaTemp const &temp) { void arena_temp_ignore(ArenaTemp const &temp) { GB_ASSERT(temp.arena); Arena *arena = temp.arena; - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); GB_ASSERT_MSG(arena->temp_count > 0, "double-use of arena_temp_end"); arena->temp_count -= 1; @@ -370,14 +374,65 @@ gb_internal GB_ALLOCATOR_PROC(arena_allocator_proc) { } -gb_global gb_thread_local Arena permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; -gb_internal gbAllocator permanent_allocator() { - return arena_allocator(&permanent_arena); +enum ThreadArenaKind : uintptr { + ThreadArena_Permanent, + ThreadArena_Temporary, +}; + +gb_global Arena default_permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; +gb_global Arena default_temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; + + +gb_internal Arena *get_arena(ThreadArenaKind kind) { + Thread *t = get_current_thread(); + switch (kind) { + case ThreadArena_Permanent: return t ? t->permanent_arena : &default_permanent_arena; + case ThreadArena_Temporary: return t ? t->temporary_arena : &default_temporary_arena; + } + GB_PANIC("INVALID ARENA KIND"); + return nullptr; +} + + + +gb_internal GB_ALLOCATOR_PROC(thread_arena_allocator_proc) { + void *ptr = nullptr; + ThreadArenaKind kind = cast(ThreadArenaKind)cast(uintptr)allocator_data; + Arena *arena = get_arena(kind); + + switch (type) { + case gbAllocation_Alloc: + ptr = arena_alloc(arena, size, alignment); + break; + case gbAllocation_Free: + break; + case gbAllocation_Resize: + if (size == 0) { + ptr = nullptr; + } else if (size <= old_size) { + ptr = old_memory; + } else { + ptr = arena_alloc(arena, size, alignment); + gb_memmove(ptr, old_memory, old_size); + } + break; + case gbAllocation_FreeAll: + GB_PANIC("use arena_free_all directly"); + arena_free_all(arena); + break; + } + + return ptr; +} + + + +gb_internal gbAllocator permanent_allocator() { + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } -gb_global gb_thread_local Arena temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; gb_internal gbAllocator temporary_allocator() { - return arena_allocator(&temporary_arena); + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } @@ -385,7 +440,7 @@ gb_internal gbAllocator temporary_allocator() { // #define TEMPORARY_ALLOCATOR_GUARD() -#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(&temporary_arena) +#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(get_arena(ThreadArena_Temporary)) #define PERMANENT_ALLOCATOR_GUARD() diff --git a/src/entity.cpp b/src/entity.cpp index 8f55c1faf..db6ffdd52 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -45,7 +45,7 @@ enum EntityFlag : u64 { EntityFlag_Value = 1ull<<11, EntityFlag_BitFieldField = 1ull<<12, - + EntityFlag_NoCapture = 1ull<<13, // #no_capture EntityFlag_PolyConst = 1ull<<15, EntityFlag_NotExported = 1ull<<16, @@ -133,9 +133,7 @@ enum EntityConstantFlags : u32 { enum ProcedureOptimizationMode : u8 { ProcedureOptimizationMode_Default, ProcedureOptimizationMode_None, - ProcedureOptimizationMode_Minimal, - ProcedureOptimizationMode_Size, - ProcedureOptimizationMode_Speed, + ProcedureOptimizationMode_FavorSize, }; @@ -338,6 +336,9 @@ gb_internal Entity *alloc_entity(EntityKind kind, Scope *scope, Token token, Typ entity->token = token; entity->type = type; entity->id = 1 + global_entity_id.fetch_add(1); + if (token.pos.file_id) { + entity->file = thread_safe_get_ast_file_from_id(token.pos.file_id); + } return entity; } diff --git a/src/gb/gb.h b/src/gb/gb.h index 22a30a04b..38dabc9bd 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -2534,7 +2534,7 @@ gb_inline void const *gb_pointer_add_const(void const *ptr, isize bytes) { gb_inline void const *gb_pointer_sub_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr - bytes); } gb_inline isize gb_pointer_diff (void const *begin, void const *end) { return cast(isize)(cast(u8 const *)end - cast(u8 const *)begin); } -gb_inline void gb_zero_size(void *ptr, isize size) { gb_memset(ptr, 0, size); } +gb_inline void gb_zero_size(void *ptr, isize size) { memset(ptr, 0, size); } #if defined(_MSC_VER) && !defined(__clang__) diff --git a/src/linker.cpp b/src/linker.cpp index 371736743..046e72d0e 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -85,6 +85,20 @@ gb_internal i32 linker_stage(LinkerData *gen) { 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 = e->LibraryName.paths[i]; + + if (lib.len == 0) { + continue; + } + + if (!string_ends_with(lib, str_lit(".o"))) { + continue; + } + + inputs = gb_string_append_fmt(inputs, " \"%.*s\"", LIT(lib)); + } } if (build_context.metrics.os == TargetOs_orca) { @@ -208,7 +222,21 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (has_asm_extension(lib)) { if (!string_set_update(&asm_files, lib)) { String asm_file = lib; - String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); + String obj_file = {}; + String temp_dir = temporary_directory(temporary_allocator()); + if (temp_dir.len != 0) { + String filename = filename_without_directory(asm_file); + + gbString str = gb_string_make(heap_allocator(), ""); + str = gb_string_append_length(str, temp_dir.text, temp_dir.len); + str = gb_string_appendc(str, "/"); + str = gb_string_append_length(str, filename.text, filename.len); + str = gb_string_append_fmt(str, "-%p.obj", asm_file.text); + obj_file = make_string_c(str); + } else { + obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); + } + String obj_format = str_lit("win64"); #if defined(GB_ARCH_32_BIT) obj_format = str_lit("win32"); @@ -277,30 +305,30 @@ gb_internal i32 linker_stage(LinkerData *gen) { defer (gb_free(heap_allocator(), windows_sdk_bin_path.text)); if (!build_context.use_lld) { // msvc - String res_path = {}; + String res_path = quote_path(heap_allocator(), build_context.build_paths[BuildPath_RES]); + String rc_path = quote_path(heap_allocator(), build_context.build_paths[BuildPath_RC]); defer (gb_free(heap_allocator(), res_path.text)); + defer (gb_free(heap_allocator(), rc_path.text)); - // TODO(Jeroen): Add ability to reuse .res file instead of recompiling, if `-resource:file.res` is given. if (build_context.has_resource) { - String temp_res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]); - res_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_res_path, str_lit("\"")); - gb_free(heap_allocator(), temp_res_path.text); + if (build_context.build_paths[BuildPath_RC].basename == "") { + debugf("Using precompiled resource %.*s\n", LIT(res_path)); + } else { + debugf("Compiling resource %.*s\n", LIT(res_path)); - String temp_rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]); - String rc_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_rc_path, str_lit("\"")); - gb_free(heap_allocator(), temp_rc_path.text); - defer (gb_free(heap_allocator(), rc_path.text)); + result = system_exec_command_line_app("msvc-link", + "\"%.*src.exe\" /nologo /fo %.*s %.*s", + LIT(windows_sdk_bin_path), + LIT(res_path), + LIT(rc_path) + ); - result = system_exec_command_line_app("msvc-link", - "\"%.*src.exe\" /nologo /fo %.*s %.*s", - LIT(windows_sdk_bin_path), - LIT(res_path), - LIT(rc_path) - ); - - if (result) { - return result; + if (result) { + return result; + } } + } else { + res_path = {}; } String linker_name = str_lit("link.exe"); @@ -399,7 +427,22 @@ gb_internal i32 linker_stage(LinkerData *gen) { continue; // already handled } String asm_file = lib; - String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".o")); + String obj_file = {}; + + String temp_dir = temporary_directory(temporary_allocator()); + if (temp_dir.len != 0) { + String filename = filename_without_directory(asm_file); + + gbString str = gb_string_make(heap_allocator(), ""); + str = gb_string_append_length(str, temp_dir.text, temp_dir.len); + str = gb_string_appendc(str, "/"); + str = gb_string_append_length(str, filename.text, filename.len); + str = gb_string_append_fmt(str, "-%p.o", asm_file.text); + obj_file = make_string_c(str); + } else { + obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".o")); + } + String obj_format; #if defined(GB_ARCH_64_BIT) if (is_osx) { diff --git a/src/llvm-c/Analysis.h b/src/llvm-c/Analysis.h index 270b145a4..6b93b5c3d 100644 --- a/src/llvm-c/Analysis.h +++ b/src/llvm-c/Analysis.h @@ -19,8 +19,8 @@ #ifndef LLVM_C_ANALYSIS_H #define LLVM_C_ANALYSIS_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/BitReader.h b/src/llvm-c/BitReader.h index 088107468..725f3fa84 100644 --- a/src/llvm-c/BitReader.h +++ b/src/llvm-c/BitReader.h @@ -19,8 +19,8 @@ #ifndef LLVM_C_BITREADER_H #define LLVM_C_BITREADER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/BitWriter.h b/src/llvm-c/BitWriter.h index ea84b6593..ba4a61afc 100644 --- a/src/llvm-c/BitWriter.h +++ b/src/llvm-c/BitWriter.h @@ -19,8 +19,8 @@ #ifndef LLVM_C_BITWRITER_H #define LLVM_C_BITWRITER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Comdat.h b/src/llvm-c/Comdat.h index 8002bc058..30df20799 100644 --- a/src/llvm-c/Comdat.h +++ b/src/llvm-c/Comdat.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_COMDAT_H #define LLVM_C_COMDAT_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Config/AsmParsers.def b/src/llvm-c/Config/AsmParsers.def index 7aaab6e50..a641f395b 100644 --- a/src/llvm-c/Config/AsmParsers.def +++ b/src/llvm-c/Config/AsmParsers.def @@ -27,18 +27,20 @@ LLVM_ASM_PARSER(AArch64) LLVM_ASM_PARSER(AMDGPU) LLVM_ASM_PARSER(ARM) +LLVM_ASM_PARSER(AVR) LLVM_ASM_PARSER(BPF) LLVM_ASM_PARSER(Hexagon) LLVM_ASM_PARSER(Lanai) +LLVM_ASM_PARSER(LoongArch) LLVM_ASM_PARSER(Mips) LLVM_ASM_PARSER(MSP430) LLVM_ASM_PARSER(PowerPC) LLVM_ASM_PARSER(RISCV) LLVM_ASM_PARSER(Sparc) LLVM_ASM_PARSER(SystemZ) +LLVM_ASM_PARSER(VE) LLVM_ASM_PARSER(WebAssembly) LLVM_ASM_PARSER(X86) -LLVM_ASM_PARSER(AVR) #undef LLVM_ASM_PARSER diff --git a/src/llvm-c/Config/AsmPrinters.def b/src/llvm-c/Config/AsmPrinters.def index 3ecc3644f..c463c3e89 100644 --- a/src/llvm-c/Config/AsmPrinters.def +++ b/src/llvm-c/Config/AsmPrinters.def @@ -27,9 +27,11 @@ LLVM_ASM_PRINTER(AArch64) LLVM_ASM_PRINTER(AMDGPU) LLVM_ASM_PRINTER(ARM) +LLVM_ASM_PRINTER(AVR) LLVM_ASM_PRINTER(BPF) LLVM_ASM_PRINTER(Hexagon) LLVM_ASM_PRINTER(Lanai) +LLVM_ASM_PRINTER(LoongArch) LLVM_ASM_PRINTER(Mips) LLVM_ASM_PRINTER(MSP430) LLVM_ASM_PRINTER(NVPTX) @@ -37,10 +39,10 @@ LLVM_ASM_PRINTER(PowerPC) LLVM_ASM_PRINTER(RISCV) LLVM_ASM_PRINTER(Sparc) LLVM_ASM_PRINTER(SystemZ) +LLVM_ASM_PRINTER(VE) LLVM_ASM_PRINTER(WebAssembly) LLVM_ASM_PRINTER(X86) LLVM_ASM_PRINTER(XCore) -LLVM_ASM_PRINTER(AVR) #undef LLVM_ASM_PRINTER diff --git a/src/llvm-c/Config/Disassemblers.def b/src/llvm-c/Config/Disassemblers.def index 4485af241..dce865414 100644 --- a/src/llvm-c/Config/Disassemblers.def +++ b/src/llvm-c/Config/Disassemblers.def @@ -27,19 +27,21 @@ LLVM_DISASSEMBLER(AArch64) LLVM_DISASSEMBLER(AMDGPU) LLVM_DISASSEMBLER(ARM) +LLVM_DISASSEMBLER(AVR) LLVM_DISASSEMBLER(BPF) LLVM_DISASSEMBLER(Hexagon) LLVM_DISASSEMBLER(Lanai) +LLVM_DISASSEMBLER(LoongArch) LLVM_DISASSEMBLER(Mips) LLVM_DISASSEMBLER(MSP430) LLVM_DISASSEMBLER(PowerPC) LLVM_DISASSEMBLER(RISCV) LLVM_DISASSEMBLER(Sparc) LLVM_DISASSEMBLER(SystemZ) +LLVM_DISASSEMBLER(VE) LLVM_DISASSEMBLER(WebAssembly) LLVM_DISASSEMBLER(X86) LLVM_DISASSEMBLER(XCore) -LLVM_DISASSEMBLER(AVR) #undef LLVM_DISASSEMBLER diff --git a/src/llvm-c/Config/TargetExegesis.def b/src/llvm-c/Config/TargetExegesis.def new file mode 100644 index 000000000..d4d3f99a7 --- /dev/null +++ b/src/llvm-c/Config/TargetExegesis.def @@ -0,0 +1,33 @@ +/*===----- llvm/Config/TargetExegesis.def - LLVM Target Exegesis-*- C++ -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This file enumerates all of the target's of llvm-exegesis *| +|* supported by this build of LLVM. Clients of this file should define *| +|* the LLVM_EXEGISIS macro to be a function-like macro with a *| +|* single parameter (the name of the target whose assembly can be *| +|* generated); including this file will then enumerate all of the *| +|* targets with target llvm-exegsis support. *| +|* *| +|* The set of targets supported by LLVM is generated at configuration *| +|* time, at which point this header is generated. Do not modify this *| +|* header directly. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_EXEGESIS +# error Please define the macro LLVM_EXEGESIS(TargetName) +#endif + +LLVM_EXEGESIS(AArch64) +LLVM_EXEGESIS(Mips) +LLVM_EXEGESIS(PowerPC) +LLVM_EXEGESIS(X86) + + +#undef LLVM_EXEGESIS diff --git a/src/llvm-c/Config/TargetMCAs.def b/src/llvm-c/Config/TargetMCAs.def new file mode 100644 index 000000000..1aefdbc25 --- /dev/null +++ b/src/llvm-c/Config/TargetMCAs.def @@ -0,0 +1,32 @@ +/*===------ llvm/Config/TargetMCAs.def - LLVM Target MCAs -------*- C++ -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This file enumerates all of the target MCAs *| +|* supported by this build of LLVM. Clients of this file should define *| +|* the LLVM_TARGETMCA macro to be a function-like macro with a *| +|* single parameter (the name of the target whose assembly can be *| +|* generated); including this file will then enumerate all of the *| +|* targets with target MCAs. *| +|* *| +|* The set of targets supported by LLVM is generated at configuration *| +|* time, at which point this header is generated. Do not modify this *| +|* header directly. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_TARGETMCA +# error Please define the macro LLVM_TARGETMCA(TargetName) +#endif + +LLVM_TARGETMCA(AMDGPU) +LLVM_TARGETMCA(RISCV) +LLVM_TARGETMCA(X86) + + +#undef LLVM_TARGETMCA diff --git a/src/llvm-c/Config/Targets.def b/src/llvm-c/Config/Targets.def index 4962add96..250aac5c9 100644 --- a/src/llvm-c/Config/Targets.def +++ b/src/llvm-c/Config/Targets.def @@ -26,9 +26,11 @@ LLVM_TARGET(AArch64) LLVM_TARGET(AMDGPU) LLVM_TARGET(ARM) +LLVM_TARGET(AVR) LLVM_TARGET(BPF) LLVM_TARGET(Hexagon) LLVM_TARGET(Lanai) +LLVM_TARGET(LoongArch) LLVM_TARGET(Mips) LLVM_TARGET(MSP430) LLVM_TARGET(NVPTX) @@ -36,10 +38,10 @@ LLVM_TARGET(PowerPC) LLVM_TARGET(RISCV) LLVM_TARGET(Sparc) LLVM_TARGET(SystemZ) +LLVM_TARGET(VE) LLVM_TARGET(WebAssembly) LLVM_TARGET(X86) LLVM_TARGET(XCore) -LLVM_TARGET(AVR) #undef LLVM_TARGET diff --git a/src/llvm-c/Config/abi-breaking.h b/src/llvm-c/Config/abi-breaking.h index a09cffa7e..c501cc354 100644 --- a/src/llvm-c/Config/abi-breaking.h +++ b/src/llvm-c/Config/abi-breaking.h @@ -1,4 +1,4 @@ -/*===------- llvm-c/Config//abi-breaking.h - llvm configuration -------*- C -*-===*/ +/*===------- llvm/Config/abi-breaking.h - llvm configuration -------*- C -*-===*/ /* */ /* Part of the LLVM Project, under the Apache License v2.0 with LLVM */ /* Exceptions. */ @@ -20,7 +20,7 @@ /* Allow selectively disabling link-time mismatch checking so that header-only ADT content from LLVM can be used without linking libSupport. */ -#if !LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING +#if !defined(LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING) || !LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING // ABI_BREAKING_CHECKS protection: provides link-time failure when clients build // mismatch with LLVM diff --git a/src/llvm-c/Config/llvm-config.h b/src/llvm-c/Config/llvm-config.h index 331d05093..e4edb83c5 100644 --- a/src/llvm-c/Config/llvm-config.h +++ b/src/llvm-c/Config/llvm-config.h @@ -1,4 +1,4 @@ -/*===------- llvm-c/Config//llvm-config.h - llvm configuration -------*- C -*-===*/ +/*===------- llvm/Config/llvm-config.h - llvm configuration -------*- C -*-===*/ /* */ /* Part of the LLVM Project, under the Apache License v2.0 with LLVM */ /* Exceptions. */ @@ -17,10 +17,8 @@ /* Define if LLVM_ENABLE_DUMP is enabled */ /* #undef LLVM_ENABLE_DUMP */ -/* Define if we link Polly to the tools */ -/* #undef LINK_POLLY_INTO_TOOLS */ - /* Target triple LLVM will generate code for by default */ +/* Doesn't use `cmakedefine` because it is allowed to be empty. */ #define LLVM_DEFAULT_TARGET_TRIPLE "x86_64-pc-windows-msvc" /* Define if threads enabled */ @@ -53,6 +51,84 @@ /* LLVM name for the native target MC init function, if available */ #define LLVM_NATIVE_TARGETMC LLVMInitializeX86TargetMC +/* LLVM name for the native target MCA init function, if available */ +/* #undef LLVM_NATIVE_TARGETMCA */ + +/* Define if the AArch64 target is built in */ +#define LLVM_HAS_AARCH64_TARGET 1 + +/* Define if the AMDGPU target is built in */ +#define LLVM_HAS_AMDGPU_TARGET 1 + +/* Define if the ARC target is built in */ +#define LLVM_HAS_ARC_TARGET 0 + +/* Define if the ARM target is built in */ +#define LLVM_HAS_ARM_TARGET 1 + +/* Define if the AVR target is built in */ +#define LLVM_HAS_AVR_TARGET 1 + +/* Define if the BPF target is built in */ +#define LLVM_HAS_BPF_TARGET 1 + +/* Define if the CSKY target is built in */ +#define LLVM_HAS_CSKY_TARGET 0 + +/* Define if the DirectX target is built in */ +#define LLVM_HAS_DIRECTX_TARGET 0 + +/* Define if the Hexagon target is built in */ +#define LLVM_HAS_HEXAGON_TARGET 1 + +/* Define if the Lanai target is built in */ +#define LLVM_HAS_LANAI_TARGET 1 + +/* Define if the LoongArch target is built in */ +#define LLVM_HAS_LOONGARCH_TARGET 1 + +/* Define if the M68k target is built in */ +#define LLVM_HAS_M68K_TARGET 0 + +/* Define if the Mips target is built in */ +#define LLVM_HAS_MIPS_TARGET 1 + +/* Define if the MSP430 target is built in */ +#define LLVM_HAS_MSP430_TARGET 1 + +/* Define if the NVPTX target is built in */ +#define LLVM_HAS_NVPTX_TARGET 1 + +/* Define if the PowerPC target is built in */ +#define LLVM_HAS_POWERPC_TARGET 1 + +/* Define if the RISCV target is built in */ +#define LLVM_HAS_RISCV_TARGET 1 + +/* Define if the Sparc target is built in */ +#define LLVM_HAS_SPARC_TARGET 1 + +/* Define if the SPIRV target is built in */ +#define LLVM_HAS_SPIRV_TARGET 0 + +/* Define if the SystemZ target is built in */ +#define LLVM_HAS_SYSTEMZ_TARGET 1 + +/* Define if the VE target is built in */ +#define LLVM_HAS_VE_TARGET 1 + +/* Define if the WebAssembly target is built in */ +#define LLVM_HAS_WEBASSEMBLY_TARGET 1 + +/* Define if the X86 target is built in */ +#define LLVM_HAS_X86_TARGET 1 + +/* Define if the XCore target is built in */ +#define LLVM_HAS_XCORE_TARGET 1 + +/* Define if the Xtensa target is built in */ +#define LLVM_HAS_XTENSA_TARGET 0 + /* Define if this is Unixish platform */ /* #undef LLVM_ON_UNIX */ @@ -66,20 +142,60 @@ #define LLVM_USE_PERF 0 /* Major version of the LLVM API */ -#define LLVM_VERSION_MAJOR 17 +#define LLVM_VERSION_MAJOR 18 /* Minor version of the LLVM API */ -#define LLVM_VERSION_MINOR 0 +#define LLVM_VERSION_MINOR 1 /* Patch version of the LLVM API */ -#define LLVM_VERSION_PATCH 1 +#define LLVM_VERSION_PATCH 8 /* LLVM version string */ -#define LLVM_VERSION_STRING "17.0.1" +#define LLVM_VERSION_STRING "18.1.8" /* Whether LLVM records statistics for use with GetStatistics(), * PrintStatistics() or PrintStatisticsJSON() */ #define LLVM_FORCE_ENABLE_STATS 0 +/* Define if we have z3 and want to build it */ +/* #undef LLVM_WITH_Z3 */ + +/* Define if we have curl and want to use it */ +/* #undef LLVM_ENABLE_CURL */ + +/* Define if we have cpp-httplib and want to use it */ +/* #undef LLVM_ENABLE_HTTPLIB */ + +/* Define if zlib compression is available */ +#define LLVM_ENABLE_ZLIB 0 + +/* Define if zstd compression is available */ +#define LLVM_ENABLE_ZSTD 0 + +/* Define if LLVM is using tflite */ +/* #undef LLVM_HAVE_TFLITE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYSEXITS_H */ + +/* Define if building libLLVM shared library */ +/* #undef LLVM_BUILD_LLVM_DYLIB */ + +/* Define if building LLVM with BUILD_SHARED_LIBS */ +/* #undef LLVM_BUILD_SHARED_LIBS */ + +/* Define if building LLVM with LLVM_FORCE_USE_OLD_TOOLCHAIN_LIBS */ +/* #undef LLVM_FORCE_USE_OLD_TOOLCHAIN */ + +/* Define if llvm_unreachable should be optimized with undefined behavior + * in non assert builds */ +#define LLVM_UNREACHABLE_OPTIMIZE 1 + +/* Define to 1 if you have the DIA SDK installed, and to 0 if you don't. */ +#define LLVM_ENABLE_DIA_SDK 1 + +/* Define if plugins enabled */ +/* #undef LLVM_ENABLE_PLUGINS */ + #endif diff --git a/src/llvm-c/Core.h b/src/llvm-c/Core.h index fbba8ca42..25b8248fd 100644 --- a/src/llvm-c/Core.h +++ b/src/llvm-c/Core.h @@ -15,11 +15,11 @@ #ifndef LLVM_C_CORE_H #define LLVM_C_CORE_H -#include "llvm-c/Deprecated.h" -#include "llvm-c/ErrorHandling.h" -#include "llvm-c/ExternC.h" +#include "Deprecated.h" +#include "ErrorHandling.h" +#include "ExternC.h" -#include "llvm-c/Types.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN @@ -216,7 +216,6 @@ typedef enum { LLVMColdCallConv = 9, LLVMGHCCallConv = 10, LLVMHiPECallConv = 11, - LLVMWebKitJSCallConv = 12, LLVMAnyRegCallConv = 13, LLVMPreserveMostCallConv = 14, LLVMPreserveAllCallConv = 15, @@ -468,8 +467,45 @@ enum { LLVMAttributeFunctionIndex = -1, }; +/** + * Tail call kind for LLVMSetTailCallKind and LLVMGetTailCallKind. + * + * Note that 'musttail' implies 'tail'. + * + * @see CallInst::TailCallKind + */ +typedef enum { + LLVMTailCallKindNone = 0, + LLVMTailCallKindTail = 1, + LLVMTailCallKindMustTail = 2, + LLVMTailCallKindNoTail = 3, +} LLVMTailCallKind; + typedef unsigned LLVMAttributeIndex; +enum { + LLVMFastMathAllowReassoc = (1 << 0), + LLVMFastMathNoNaNs = (1 << 1), + LLVMFastMathNoInfs = (1 << 2), + LLVMFastMathNoSignedZeros = (1 << 3), + LLVMFastMathAllowReciprocal = (1 << 4), + LLVMFastMathAllowContract = (1 << 5), + LLVMFastMathApproxFunc = (1 << 6), + LLVMFastMathNone = 0, + LLVMFastMathAll = LLVMFastMathAllowReassoc | LLVMFastMathNoNaNs | + LLVMFastMathNoInfs | LLVMFastMathNoSignedZeros | + LLVMFastMathAllowReciprocal | LLVMFastMathAllowContract | + LLVMFastMathApproxFunc, +}; + +/** + * Flags to indicate what fast-math-style optimizations are allowed + * on operations. + * + * See https://llvm.org/docs/LangRef.html#fast-math-flags + */ +typedef unsigned LLVMFastMathFlags; + /** * @} */ @@ -890,12 +926,58 @@ void LLVMAppendModuleInlineAsm(LLVMModuleRef M, const char *Asm, size_t Len); * * @see InlineAsm::get() */ -LLVMValueRef LLVMGetInlineAsm(LLVMTypeRef Ty, char *AsmString, - size_t AsmStringSize, char *Constraints, +LLVMValueRef LLVMGetInlineAsm(LLVMTypeRef Ty, const char *AsmString, + size_t AsmStringSize, const char *Constraints, size_t ConstraintsSize, LLVMBool HasSideEffects, LLVMBool IsAlignStack, LLVMInlineAsmDialect Dialect, LLVMBool CanThrow); +/** + * Get the template string used for an inline assembly snippet + * + */ +const char *LLVMGetInlineAsmAsmString(LLVMValueRef InlineAsmVal, size_t *Len); + +/** + * Get the raw constraint string for an inline assembly snippet + * + */ +const char *LLVMGetInlineAsmConstraintString(LLVMValueRef InlineAsmVal, + size_t *Len); + +/** + * Get the dialect used by the inline asm snippet + * + */ +LLVMInlineAsmDialect LLVMGetInlineAsmDialect(LLVMValueRef InlineAsmVal); + +/** + * Get the function type of the inline assembly snippet. The same type that + * was passed into LLVMGetInlineAsm originally + * + * @see LLVMGetInlineAsm + * + */ +LLVMTypeRef LLVMGetInlineAsmFunctionType(LLVMValueRef InlineAsmVal); + +/** + * Get if the inline asm snippet has side effects + * + */ +LLVMBool LLVMGetInlineAsmHasSideEffects(LLVMValueRef InlineAsmVal); + +/** + * Get if the inline asm snippet needs an aligned stack + * + */ +LLVMBool LLVMGetInlineAsmNeedsAlignedStack(LLVMValueRef InlineAsmVal); + +/** + * Get if the inline asm snippet may unwind the stack + * + */ +LLVMBool LLVMGetInlineAsmCanUnwind(LLVMValueRef InlineAsmVal); + /** * Obtain the context to which this module is associated. * @@ -2216,45 +2298,26 @@ LLVMValueRef LLVMConstNUWSub(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant) LLVMValueRef LLVMConstMul(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstNSWMul(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstNUWMul(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstAnd(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstOr(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstXor(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstICmp(LLVMIntPredicate Predicate, LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstFCmp(LLVMRealPredicate Predicate, LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstShl(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstLShr(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); -LLVMValueRef LLVMConstAShr(LLVMValueRef LHSConstant, LLVMValueRef RHSConstant); LLVMValueRef LLVMConstGEP2(LLVMTypeRef Ty, LLVMValueRef ConstantVal, LLVMValueRef *ConstantIndices, unsigned NumIndices); LLVMValueRef LLVMConstInBoundsGEP2(LLVMTypeRef Ty, LLVMValueRef ConstantVal, LLVMValueRef *ConstantIndices, unsigned NumIndices); LLVMValueRef LLVMConstTrunc(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstSExt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstZExt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPTrunc(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPExt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstUIToFP(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstSIToFP(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPToUI(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstFPToSI(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstPtrToInt(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstIntToPtr(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstBitCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstAddrSpaceCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstZExtOrBitCast(LLVMValueRef ConstantVal, - LLVMTypeRef ToType); -LLVMValueRef LLVMConstSExtOrBitCast(LLVMValueRef ConstantVal, - LLVMTypeRef ToType); LLVMValueRef LLVMConstTruncOrBitCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstPointerCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); -LLVMValueRef LLVMConstIntCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType, - LLVMBool isSigned); -LLVMValueRef LLVMConstFPCast(LLVMValueRef ConstantVal, LLVMTypeRef ToType); LLVMValueRef LLVMConstExtractElement(LLVMValueRef VectorConstant, LLVMValueRef IndexConstant); LLVMValueRef LLVMConstInsertElement(LLVMValueRef VectorConstant, @@ -2960,6 +3023,74 @@ LLVMValueRef LLVMMDNodeInContext(LLVMContextRef C, LLVMValueRef *Vals, /** Deprecated: Use LLVMMDNodeInContext2 instead. */ LLVMValueRef LLVMMDNode(LLVMValueRef *Vals, unsigned Count); +/** + * @} + */ + +/** + * @defgroup LLVMCCoreOperandBundle Operand Bundles + * + * Functions in this group operate on LLVMOperandBundleRef instances that + * correspond to llvm::OperandBundleDef instances. + * + * @see llvm::OperandBundleDef + * + * @{ + */ + +/** + * Create a new operand bundle. + * + * Every invocation should be paired with LLVMDisposeOperandBundle() or memory + * will be leaked. + * + * @param Tag Tag name of the operand bundle + * @param TagLen Length of Tag + * @param Args Memory address of an array of bundle operands + * @param NumArgs Length of Args + */ +LLVMOperandBundleRef LLVMCreateOperandBundle(const char *Tag, size_t TagLen, + LLVMValueRef *Args, + unsigned NumArgs); + +/** + * Destroy an operand bundle. + * + * This must be called for every created operand bundle or memory will be + * leaked. + */ +void LLVMDisposeOperandBundle(LLVMOperandBundleRef Bundle); + +/** + * Obtain the tag of an operand bundle as a string. + * + * @param Bundle Operand bundle to obtain tag of. + * @param Len Out parameter which holds the length of the returned string. + * @return The tag name of Bundle. + * @see OperandBundleDef::getTag() + */ +const char *LLVMGetOperandBundleTag(LLVMOperandBundleRef Bundle, size_t *Len); + +/** + * Obtain the number of operands for an operand bundle. + * + * @param Bundle Operand bundle to obtain operand count of. + * @return The number of operands. + * @see OperandBundleDef::input_size() + */ +unsigned LLVMGetNumOperandBundleArgs(LLVMOperandBundleRef Bundle); + +/** + * Obtain the operand for an operand bundle at the given index. + * + * @param Bundle Operand bundle to obtain operand of. + * @param Index An operand index, must be less than + * LLVMGetNumOperandBundleArgs(). + * @return The operand. + */ +LLVMValueRef LLVMGetOperandBundleArgAtIndex(LLVMOperandBundleRef Bundle, + unsigned Index); + /** * @} */ @@ -3411,6 +3542,24 @@ LLVMTypeRef LLVMGetCalledFunctionType(LLVMValueRef C); */ LLVMValueRef LLVMGetCalledValue(LLVMValueRef Instr); +/** + * Obtain the number of operand bundles attached to this instruction. + * + * This only works on llvm::CallInst and llvm::InvokeInst instructions. + * + * @see llvm::CallBase::getNumOperandBundles() + */ +unsigned LLVMGetNumOperandBundles(LLVMValueRef C); + +/** + * Obtain the operand bundle attached to this instruction at the given index. + * Use LLVMDisposeOperandBundle to free the operand bundle. + * + * This only works on llvm::CallInst and llvm::InvokeInst instructions. + */ +LLVMOperandBundleRef LLVMGetOperandBundleAtIndex(LLVMValueRef C, + unsigned Index); + /** * Obtain whether a call instruction is a tail call. * @@ -3429,6 +3578,20 @@ LLVMBool LLVMIsTailCall(LLVMValueRef CallInst); */ void LLVMSetTailCall(LLVMValueRef CallInst, LLVMBool IsTailCall); +/** + * Obtain a tail call kind of the call instruction. + * + * @see llvm::CallInst::setTailCallKind() + */ +LLVMTailCallKind LLVMGetTailCallKind(LLVMValueRef CallInst); + +/** + * Set the call kind of the call instruction. + * + * @see llvm::CallInst::getTailCallKind() + */ +void LLVMSetTailCallKind(LLVMValueRef CallInst, LLVMTailCallKind kind); + /** * Return the normal destination basic block. * @@ -3761,6 +3924,10 @@ LLVMValueRef LLVMBuildInvoke2(LLVMBuilderRef, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, const char *Name); +LLVMValueRef LLVMBuildInvokeWithOperandBundles( + LLVMBuilderRef, LLVMTypeRef Ty, LLVMValueRef Fn, LLVMValueRef *Args, + unsigned NumArgs, LLVMBasicBlockRef Then, LLVMBasicBlockRef Catch, + LLVMOperandBundleRef *Bundles, unsigned NumBundles, const char *Name); LLVMValueRef LLVMBuildUnreachable(LLVMBuilderRef); /* Exception Handling */ @@ -3920,6 +4087,55 @@ void LLVMSetNSW(LLVMValueRef ArithInst, LLVMBool HasNSW); LLVMBool LLVMGetExact(LLVMValueRef DivOrShrInst); void LLVMSetExact(LLVMValueRef DivOrShrInst, LLVMBool IsExact); +/** + * Gets if the instruction has the non-negative flag set. + * Only valid for zext instructions. + */ +LLVMBool LLVMGetNNeg(LLVMValueRef NonNegInst); +/** + * Sets the non-negative flag for the instruction. + * Only valid for zext instructions. + */ +void LLVMSetNNeg(LLVMValueRef NonNegInst, LLVMBool IsNonNeg); + +/** + * Get the flags for which fast-math-style optimizations are allowed for this + * value. + * + * Only valid on floating point instructions. + * @see LLVMCanValueUseFastMathFlags + */ +LLVMFastMathFlags LLVMGetFastMathFlags(LLVMValueRef FPMathInst); + +/** + * Sets the flags for which fast-math-style optimizations are allowed for this + * value. + * + * Only valid on floating point instructions. + * @see LLVMCanValueUseFastMathFlags + */ +void LLVMSetFastMathFlags(LLVMValueRef FPMathInst, LLVMFastMathFlags FMF); + +/** + * Check if a given value can potentially have fast math flags. + * + * Will return true for floating point arithmetic instructions, and for select, + * phi, and call instructions whose type is a floating point type, or a vector + * or array thereof. See https://llvm.org/docs/LangRef.html#fast-math-flags + */ +LLVMBool LLVMCanValueUseFastMathFlags(LLVMValueRef Inst); + +/** + * Gets whether the instruction has the disjoint flag set. + * Only valid for or instructions. + */ +LLVMBool LLVMGetIsDisjoint(LLVMValueRef Inst); +/** + * Sets the disjoint flag for the instruction. + * Only valid for or instructions. + */ +void LLVMSetIsDisjoint(LLVMValueRef Inst, LLVMBool IsDisjoint); + /* Memory */ LLVMValueRef LLVMBuildMalloc(LLVMBuilderRef, LLVMTypeRef Ty, const char *Name); LLVMValueRef LLVMBuildArrayMalloc(LLVMBuilderRef, LLVMTypeRef Ty, @@ -4045,6 +4261,11 @@ LLVMValueRef LLVMBuildPhi(LLVMBuilderRef, LLVMTypeRef Ty, const char *Name); LLVMValueRef LLVMBuildCall2(LLVMBuilderRef, LLVMTypeRef, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, const char *Name); +LLVMValueRef +LLVMBuildCallWithOperandBundles(LLVMBuilderRef, LLVMTypeRef, LLVMValueRef Fn, + LLVMValueRef *Args, unsigned NumArgs, + LLVMOperandBundleRef *Bundles, + unsigned NumBundles, const char *Name); LLVMValueRef LLVMBuildSelect(LLVMBuilderRef, LLVMValueRef If, LLVMValueRef Then, LLVMValueRef Else, const char *Name); diff --git a/src/llvm-c/DebugInfo.h b/src/llvm-c/DebugInfo.h index 592429470..93bd9e2ad 100644 --- a/src/llvm-c/DebugInfo.h +++ b/src/llvm-c/DebugInfo.h @@ -16,8 +16,8 @@ #ifndef LLVM_C_DEBUGINFO_H #define LLVM_C_DEBUGINFO_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Disassembler.h b/src/llvm-c/Disassembler.h index b1cb35da6..e6642f4ed 100644 --- a/src/llvm-c/Disassembler.h +++ b/src/llvm-c/Disassembler.h @@ -15,8 +15,8 @@ #ifndef LLVM_C_DISASSEMBLER_H #define LLVM_C_DISASSEMBLER_H -#include "llvm-c/DisassemblerTypes.h" -#include "llvm-c/ExternC.h" +#include "DisassemblerTypes.h" +#include "ExternC.h" /** * @defgroup LLVMCDisassembler Disassembler diff --git a/src/llvm-c/DisassemblerTypes.h b/src/llvm-c/DisassemblerTypes.h index 6999a350e..6b7ad6104 100644 --- a/src/llvm-c/DisassemblerTypes.h +++ b/src/llvm-c/DisassemblerTypes.h @@ -10,7 +10,7 @@ #ifndef LLVM_C_DISASSEMBLERTYPES_H #define LLVM_C_DISASSEMBLERTYPES_H -#include "llvm-c/DataTypes.h" +#include "DataTypes.h" #ifdef __cplusplus #include #else diff --git a/src/llvm-c/Error.h b/src/llvm-c/Error.h index c3baaf651..00746c701 100644 --- a/src/llvm-c/Error.h +++ b/src/llvm-c/Error.h @@ -14,7 +14,7 @@ #ifndef LLVM_C_ERROR_H #define LLVM_C_ERROR_H -#include "llvm-c/ExternC.h" +#include "ExternC.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/ErrorHandling.h b/src/llvm-c/ErrorHandling.h index d9b9f2275..7f9b50a95 100644 --- a/src/llvm-c/ErrorHandling.h +++ b/src/llvm-c/ErrorHandling.h @@ -14,7 +14,7 @@ #ifndef LLVM_C_ERRORHANDLING_H #define LLVM_C_ERRORHANDLING_H -#include "llvm-c/ExternC.h" +#include "ExternC.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/ExecutionEngine.h b/src/llvm-c/ExecutionEngine.h index c5fc9bdb4..8e72faefd 100644 --- a/src/llvm-c/ExecutionEngine.h +++ b/src/llvm-c/ExecutionEngine.h @@ -19,10 +19,10 @@ #ifndef LLVM_C_EXECUTIONENGINE_H #define LLVM_C_EXECUTIONENGINE_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Target.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Target.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/IRReader.h b/src/llvm-c/IRReader.h index 905b84fa5..ec1110c7a 100644 --- a/src/llvm-c/IRReader.h +++ b/src/llvm-c/IRReader.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_IRREADER_H #define LLVM_C_IRREADER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/LLJIT.h b/src/llvm-c/LLJIT.h index a06133aac..ee207e10e 100644 --- a/src/llvm-c/LLJIT.h +++ b/src/llvm-c/LLJIT.h @@ -1,4 +1,4 @@ -/*===----------- llvm-c/LLJIT.h - OrcV2 LLJIT C bindings --------*- C++ -*-===*\ +/*===----------- llvm-c/LLJIT.h - OrcV2 LLJIT C bindings ----------*- C -*-===*\ |* *| |* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| |* Exceptions. *| @@ -24,10 +24,10 @@ #ifndef LLVM_C_LLJIT_H #define LLVM_C_LLJIT_H -#include "llvm-c/Error.h" -#include "llvm-c/Orc.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "Error.h" +#include "Orc.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/LLJITUtils.h b/src/llvm-c/LLJITUtils.h new file mode 100644 index 000000000..57ffedff8 --- /dev/null +++ b/src/llvm-c/LLJITUtils.h @@ -0,0 +1,52 @@ +/*===------- llvm-c/LLJITUtils.h - Advanced LLJIT features --------*- C -*-===*\ +|* *| +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *| +|* Exceptions. *| +|* See https://llvm.org/LICENSE.txt for license information. *| +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header declares the C interface for extra utilities to be used with *| +|* the LLJIT class from the llvm-c/LLJIT.h header. It requires to following *| +|* link libraries in addition to libLLVMOrcJIT.a: *| +|* - libLLVMOrcDebugging.a *| +|* *| +|* Many exotic languages can interoperate with C code but have a harder time *| +|* with C++ due to name mangling. So in addition to C, this interface enables *| +|* tools written in such languages. *| +|* *| +|* Note: This interface is experimental. It is *NOT* stable, and may be *| +|* changed without warning. Only C API usage documentation is *| +|* provided. See the C++ documentation for all higher level ORC API *| +|* details. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_C_LLJITUTILS_H +#define LLVM_C_LLJITUTILS_H + +#include "LLJIT.h" + +LLVM_C_EXTERN_C_BEGIN + +/** + * @defgroup LLVMCExecutionEngineLLJITUtils LLJIT Utilities + * @ingroup LLVMCExecutionEngineLLJIT + * + * @{ + */ + +/** + * Install the plugin that submits debug objects to the executor. Executors must + * expose the llvm_orc_registerJITLoaderGDBWrapper symbol. + */ +LLVMErrorRef LLVMOrcLLJITEnableDebugSupport(LLVMOrcLLJITRef J); + +/** + * @} + */ + +LLVM_C_EXTERN_C_END + +#endif /* LLVM_C_LLJITUTILS_H */ diff --git a/src/llvm-c/Linker.h b/src/llvm-c/Linker.h index acff5d5e2..463a2cff9 100644 --- a/src/llvm-c/Linker.h +++ b/src/llvm-c/Linker.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_LINKER_H #define LLVM_C_LINKER_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Object.h b/src/llvm-c/Object.h index f871d5230..1948c3c34 100644 --- a/src/llvm-c/Object.h +++ b/src/llvm-c/Object.h @@ -19,9 +19,9 @@ #ifndef LLVM_C_OBJECT_H #define LLVM_C_OBJECT_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" -#include "llvm-c/Config//llvm-config.h" +#include "ExternC.h" +#include "Types.h" +#include "Config/llvm-config.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Orc.h b/src/llvm-c/Orc.h index 0dcfb0686..ecd110b4d 100644 --- a/src/llvm-c/Orc.h +++ b/src/llvm-c/Orc.h @@ -27,9 +27,9 @@ #ifndef LLVM_C_ORC_H #define LLVM_C_ORC_H -#include "llvm-c/Error.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "Error.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN @@ -346,7 +346,7 @@ typedef struct LLVMOrcOpaqueLookupState *LLVMOrcLookupStateRef; * into. * * The JDLookupFlags argument can be inspected to determine whether the original - * lookup included non-exported symobls. + * lookup included non-exported symbols. * * Finally, the LookupSet argument contains the set of symbols that could not * be found in JD already (the set of generation candidates). @@ -508,7 +508,7 @@ void LLVMOrcSymbolStringPoolClearDeadEntries(LLVMOrcSymbolStringPoolRef SSP); * Intern a string in the ExecutionSession's SymbolStringPool and return a * reference to it. This increments the ref-count of the pool entry, and the * returned value should be released once the client is done with it by - * calling LLVMOrReleaseSymbolStringPoolEntry. + * calling LLVMOrcReleaseSymbolStringPoolEntry. * * Since strings are uniqued within the SymbolStringPool * LLVMOrcSymbolStringPoolEntryRefs can be compared by value to test string @@ -796,7 +796,7 @@ void LLVMOrcDisposeSymbols(LLVMOrcSymbolStringPoolEntryRef *Symbols); * method returns an error then clients should log it and call * LLVMOrcMaterializationResponsibilityFailMaterialization. If no dependencies * have been registered for the symbols covered by this - * MaterializationResponsibiility then this method is guaranteed to return + * MaterializationResponsibility then this method is guaranteed to return * LLVMErrorSuccess. */ LLVMErrorRef LLVMOrcMaterializationResponsibilityNotifyResolved( @@ -813,7 +813,7 @@ LLVMErrorRef LLVMOrcMaterializationResponsibilityNotifyResolved( * method returns an error then clients should log it and call * LLVMOrcMaterializationResponsibilityFailMaterialization. * If no dependencies have been registered for the symbols covered by this - * MaterializationResponsibiility then this method is guaranteed to return + * MaterializationResponsibility then this method is guaranteed to return * LLVMErrorSuccess. */ LLVMErrorRef LLVMOrcMaterializationResponsibilityNotifyEmitted( @@ -839,7 +839,7 @@ LLVMErrorRef LLVMOrcMaterializationResponsibilityDefineMaterializing( /** * Notify all not-yet-emitted covered by this MaterializationResponsibility * instance that an error has occurred. - * This will remove all symbols covered by this MaterializationResponsibilty + * This will remove all symbols covered by this MaterializationResponsibility * from the target JITDylib, and send an error to any queries waiting on * these symbols. */ diff --git a/src/llvm-c/OrcEE.h b/src/llvm-c/OrcEE.h index d451187aa..aef24c7aa 100644 --- a/src/llvm-c/OrcEE.h +++ b/src/llvm-c/OrcEE.h @@ -24,11 +24,11 @@ #ifndef LLVM_C_ORCEE_H #define LLVM_C_ORCEE_H -#include "llvm-c/Error.h" -#include "llvm-c/ExecutionEngine.h" -#include "llvm-c/Orc.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "Error.h" +#include "ExecutionEngine.h" +#include "Orc.h" +#include "TargetMachine.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Remarks.h b/src/llvm-c/Remarks.h index ffe647a65..548a4041a 100644 --- a/src/llvm-c/Remarks.h +++ b/src/llvm-c/Remarks.h @@ -15,8 +15,8 @@ #ifndef LLVM_C_REMARKS_H #define LLVM_C_REMARKS_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Types.h" #ifdef __cplusplus #include #else diff --git a/src/llvm-c/Support.h b/src/llvm-c/Support.h index 17657861b..31a75354c 100644 --- a/src/llvm-c/Support.h +++ b/src/llvm-c/Support.h @@ -14,9 +14,9 @@ #ifndef LLVM_C_SUPPORT_H #define LLVM_C_SUPPORT_H -#include "llvm-c/DataTypes.h" -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" +#include "DataTypes.h" +#include "ExternC.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN diff --git a/src/llvm-c/Target.h b/src/llvm-c/Target.h index 2bc3d1ae7..4d03741c4 100644 --- a/src/llvm-c/Target.h +++ b/src/llvm-c/Target.h @@ -19,9 +19,9 @@ #ifndef LLVM_C_TARGET_H #define LLVM_C_TARGET_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Types.h" -#include "llvm-c/Config//llvm-config.h" +#include "ExternC.h" +#include "Types.h" +#include "Config/llvm-config.h" LLVM_C_EXTERN_C_BEGIN @@ -40,34 +40,34 @@ typedef struct LLVMOpaqueTargetLibraryInfotData *LLVMTargetLibraryInfoRef; /* Declare all of the target-initialization functions that are available. */ #define LLVM_TARGET(TargetName) \ void LLVMInitialize##TargetName##TargetInfo(void); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ #define LLVM_TARGET(TargetName) void LLVMInitialize##TargetName##Target(void); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ #define LLVM_TARGET(TargetName) \ void LLVMInitialize##TargetName##TargetMC(void); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ /* Declare all of the available assembly printer initialization functions. */ #define LLVM_ASM_PRINTER(TargetName) \ void LLVMInitialize##TargetName##AsmPrinter(void); -#include "llvm-c/Config//AsmPrinters.def" +#include "Config/AsmPrinters.def" #undef LLVM_ASM_PRINTER /* Explicit undef to make SWIG happier */ /* Declare all of the available assembly parser initialization functions. */ #define LLVM_ASM_PARSER(TargetName) \ void LLVMInitialize##TargetName##AsmParser(void); -#include "llvm-c/Config//AsmParsers.def" +#include "Config/AsmParsers.def" #undef LLVM_ASM_PARSER /* Explicit undef to make SWIG happier */ /* Declare all of the available disassembler initialization functions. */ #define LLVM_DISASSEMBLER(TargetName) \ void LLVMInitialize##TargetName##Disassembler(void); -#include "llvm-c/Config//Disassemblers.def" +#include "Config/Disassemblers.def" #undef LLVM_DISASSEMBLER /* Explicit undef to make SWIG happier */ /** LLVMInitializeAllTargetInfos - The main program should call this function if @@ -75,7 +75,7 @@ typedef struct LLVMOpaqueTargetLibraryInfotData *LLVMTargetLibraryInfoRef; support. */ static inline void LLVMInitializeAllTargetInfos(void) { #define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##TargetInfo(); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ } @@ -84,7 +84,7 @@ static inline void LLVMInitializeAllTargetInfos(void) { support. */ static inline void LLVMInitializeAllTargets(void) { #define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##Target(); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ } @@ -93,7 +93,7 @@ static inline void LLVMInitializeAllTargets(void) { support. */ static inline void LLVMInitializeAllTargetMCs(void) { #define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##TargetMC(); -#include "llvm-c/Config//Targets.def" +#include "Config/Targets.def" #undef LLVM_TARGET /* Explicit undef to make SWIG happier */ } @@ -102,7 +102,7 @@ static inline void LLVMInitializeAllTargetMCs(void) { available via the TargetRegistry. */ static inline void LLVMInitializeAllAsmPrinters(void) { #define LLVM_ASM_PRINTER(TargetName) LLVMInitialize##TargetName##AsmPrinter(); -#include "llvm-c/Config//AsmPrinters.def" +#include "Config/AsmPrinters.def" #undef LLVM_ASM_PRINTER /* Explicit undef to make SWIG happier */ } @@ -111,7 +111,7 @@ static inline void LLVMInitializeAllAsmPrinters(void) { available via the TargetRegistry. */ static inline void LLVMInitializeAllAsmParsers(void) { #define LLVM_ASM_PARSER(TargetName) LLVMInitialize##TargetName##AsmParser(); -#include "llvm-c/Config//AsmParsers.def" +#include "Config/AsmParsers.def" #undef LLVM_ASM_PARSER /* Explicit undef to make SWIG happier */ } @@ -121,7 +121,7 @@ static inline void LLVMInitializeAllAsmParsers(void) { static inline void LLVMInitializeAllDisassemblers(void) { #define LLVM_DISASSEMBLER(TargetName) \ LLVMInitialize##TargetName##Disassembler(); -#include "llvm-c/Config//Disassemblers.def" +#include "Config/Disassemblers.def" #undef LLVM_DISASSEMBLER /* Explicit undef to make SWIG happier */ } diff --git a/src/llvm-c/TargetMachine.h b/src/llvm-c/TargetMachine.h index bfbe1421a..aa628e216 100644 --- a/src/llvm-c/TargetMachine.h +++ b/src/llvm-c/TargetMachine.h @@ -19,9 +19,9 @@ #ifndef LLVM_C_TARGETMACHINE_H #define LLVM_C_TARGETMACHINE_H -#include "llvm-c/ExternC.h" -#include "llvm-c/Target.h" -#include "llvm-c/Types.h" +#include "ExternC.h" +#include "Target.h" +#include "Types.h" LLVM_C_EXTERN_C_BEGIN @@ -31,6 +31,7 @@ LLVM_C_EXTERN_C_BEGIN * @{ */ +typedef struct LLVMOpaqueTargetMachineOptions *LLVMTargetMachineOptionsRef; typedef struct LLVMOpaqueTargetMachine *LLVMTargetMachineRef; typedef struct LLVMTarget *LLVMTargetRef; @@ -66,6 +67,12 @@ typedef enum { LLVMObjectFile } LLVMCodeGenFileType; +typedef enum { + LLVMGlobalISelAbortEnable, + LLVMGlobalISelAbortDisable, + LLVMGlobalISelAbortDisableWithDiag, +} LLVMGlobalISelAbortMode; + /** Returns the first llvm::Target in the registered targets list. */ LLVMTargetRef LLVMGetFirstTarget(void); /** Returns the next llvm::Target given a previous one (or null if there's none) */ @@ -98,6 +105,55 @@ LLVMBool LLVMTargetHasTargetMachine(LLVMTargetRef T); LLVMBool LLVMTargetHasAsmBackend(LLVMTargetRef T); /*===-- Target Machine ----------------------------------------------------===*/ +/** + * Create a new set of options for an llvm::TargetMachine. + * + * The returned option structure must be released with + * LLVMDisposeTargetMachineOptions() after the call to + * LLVMCreateTargetMachineWithOptions(). + */ +LLVMTargetMachineOptionsRef LLVMCreateTargetMachineOptions(void); + +/** + * Dispose of an LLVMTargetMachineOptionsRef instance. + */ +void LLVMDisposeTargetMachineOptions(LLVMTargetMachineOptionsRef Options); + +void LLVMTargetMachineOptionsSetCPU(LLVMTargetMachineOptionsRef Options, + const char *CPU); + +/** + * Set the list of features for the target machine. + * + * \param Features a comma-separated list of features. + */ +void LLVMTargetMachineOptionsSetFeatures(LLVMTargetMachineOptionsRef Options, + const char *Features); + +void LLVMTargetMachineOptionsSetABI(LLVMTargetMachineOptionsRef Options, + const char *ABI); + +void LLVMTargetMachineOptionsSetCodeGenOptLevel( + LLVMTargetMachineOptionsRef Options, LLVMCodeGenOptLevel Level); + +void LLVMTargetMachineOptionsSetRelocMode(LLVMTargetMachineOptionsRef Options, + LLVMRelocMode Reloc); + +void LLVMTargetMachineOptionsSetCodeModel(LLVMTargetMachineOptionsRef Options, + LLVMCodeModel CodeModel); + +/** + * Create a new llvm::TargetMachine. + * + * \param T the target to create a machine for. + * \param Triple a triple describing the target machine. + * \param Options additional configuration (see + * LLVMCreateTargetMachineOptions()). + */ +LLVMTargetMachineRef +LLVMCreateTargetMachineWithOptions(LLVMTargetRef T, const char *Triple, + LLVMTargetMachineOptionsRef Options); + /** Creates a new llvm::TargetMachine. See llvm::Target::createTargetMachine */ LLVMTargetMachineRef LLVMCreateTargetMachine(LLVMTargetRef T, const char *Triple, const char *CPU, const char *Features, @@ -132,6 +188,21 @@ LLVMTargetDataRef LLVMCreateTargetDataLayout(LLVMTargetMachineRef T); void LLVMSetTargetMachineAsmVerbosity(LLVMTargetMachineRef T, LLVMBool VerboseAsm); +/** Enable fast-path instruction selection. */ +void LLVMSetTargetMachineFastISel(LLVMTargetMachineRef T, LLVMBool Enable); + +/** Enable global instruction selection. */ +void LLVMSetTargetMachineGlobalISel(LLVMTargetMachineRef T, LLVMBool Enable); + +/** Set abort behaviour when global instruction selection fails to lower/select + * an instruction. */ +void LLVMSetTargetMachineGlobalISelAbort(LLVMTargetMachineRef T, + LLVMGlobalISelAbortMode Mode); + +/** Enable the MachineOutliner pass. */ +void LLVMSetTargetMachineMachineOutliner(LLVMTargetMachineRef T, + LLVMBool Enable); + /** Emits an asm or object file for the given module to the filename. This wraps several c++ only classes (among them a file stream). Returns any error in ErrorMessage. Use LLVMDisposeMessage to dispose the message. */ diff --git a/src/llvm-c/Transforms/PassBuilder.h b/src/llvm-c/Transforms/PassBuilder.h index d0466dd7f..8ad2a9982 100644 --- a/src/llvm-c/Transforms/PassBuilder.h +++ b/src/llvm-c/Transforms/PassBuilder.h @@ -14,9 +14,9 @@ #ifndef LLVM_C_TRANSFORMS_PASSBUILDER_H #define LLVM_C_TRANSFORMS_PASSBUILDER_H -#include "llvm-c/Error.h" -#include "llvm-c/TargetMachine.h" -#include "llvm-c/Types.h" +#include "../Error.h" +#include "../TargetMachine.h" +#include "../Types.h" /** * @defgroup LLVMCCoreNewPM New Pass Manager diff --git a/src/llvm-c/Types.h b/src/llvm-c/Types.h index 4e9967372..77aa7c9b4 100644 --- a/src/llvm-c/Types.h +++ b/src/llvm-c/Types.h @@ -14,8 +14,8 @@ #ifndef LLVM_C_TYPES_H #define LLVM_C_TYPES_H -#include "llvm-c/DataTypes.h" -#include "llvm-c/ExternC.h" +#include "DataTypes.h" +#include "ExternC.h" LLVM_C_EXTERN_C_BEGIN @@ -132,6 +132,11 @@ typedef struct LLVMOpaquePassManager *LLVMPassManagerRef; * @see llvm::Use */ typedef struct LLVMOpaqueUse *LLVMUseRef; +/** + * @see llvm::OperandBundleDef + */ +typedef struct LLVMOpaqueOperandBundle *LLVMOperandBundleRef; + /** * Used to represent an attributes. * diff --git a/src/llvm-c/lto.h b/src/llvm-c/lto.h index 5ceb02224..89f76c695 100644 --- a/src/llvm-c/lto.h +++ b/src/llvm-c/lto.h @@ -16,7 +16,7 @@ #ifndef LLVM_C_LTO_H #define LLVM_C_LTO_H -#include "llvm-c/ExternC.h" +#include "ExternC.h" #ifdef __cplusplus #include diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index b2e485d01..c21cd0a46 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -15,6 +15,7 @@ struct lbArgType { LLVMAttributeRef align_attribute; // Optional i64 byval_alignment; bool is_byval; + bool no_capture; }; @@ -159,6 +160,11 @@ gb_internal void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType LLVMAddAttributeAtIndex(fn, arg_index+1, arg->align_attribute); } + if (arg->no_capture) { + LLVMAddAttributeAtIndex(fn, arg_index+1, nocapture_attr); + } + + if (ft->multiple_return_original_type) { if (ft->original_arg_count <= i) { LLVMAddAttributeAtIndex(fn, arg_index+1, noalias_attr); @@ -645,10 +651,10 @@ namespace lbAbiAmd64SysV { if (is_mem_cls(cls, attribute_kind)) { LLVMAttributeRef attribute = nullptr; if (attribute_kind == Amd64TypeAttribute_ByVal) { - // if (!is_calling_convention_odin(calling_convention)) { - return lb_arg_type_indirect_byval(c, type); - // } - // attribute = nullptr; + if (is_calling_convention_odin(calling_convention)) { + return lb_arg_type_indirect(type, attribute); + } + return lb_arg_type_indirect_byval(c, type); } else if (attribute_kind == Amd64TypeAttribute_StructRect) { attribute = lb_create_enum_attribute_with_type(c, "sret", type); } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 92c8138c5..72ba12516 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1,13 +1,11 @@ #define MULTITHREAD_OBJECT_GENERATION 1 - -#ifndef USE_SEPARATE_MODULES -#define USE_SEPARATE_MODULES build_context.use_separate_modules -#endif - #ifndef MULTITHREAD_OBJECT_GENERATION #define MULTITHREAD_OBJECT_GENERATION 0 #endif +#ifndef USE_SEPARATE_MODULES +#define USE_SEPARATE_MODULES build_context.use_separate_modules +#endif #ifndef LLVM_IGNORE_VERIFICATION #define LLVM_IGNORE_VERIFICATION 0 @@ -137,19 +135,28 @@ gb_internal void lb_set_entity_from_other_modules_linkage_correctly(lbModule *ot if (other_module == nullptr) { return; } - char const *cname = alloc_cstring(temporary_allocator(), name); + char const *cname = alloc_cstring(permanent_allocator(), name); + mpsc_enqueue(&other_module->gen->entities_to_correct_linkage, lbEntityCorrection{other_module, e, cname}); +} - LLVMValueRef other_global = nullptr; - if (e->kind == Entity_Variable) { - other_global = LLVMGetNamedGlobal(other_module->mod, cname); - } else if (e->kind == Entity_Procedure) { - other_global = LLVMGetNamedFunction(other_module->mod, cname); - } - if (other_global) { - LLVMSetLinkage(other_global, LLVMExternalLinkage); +gb_internal void lb_correct_entity_linkage(lbGenerator *gen) { + for (lbEntityCorrection ec = {}; mpsc_dequeue(&gen->entities_to_correct_linkage, &ec); /**/) { + LLVMValueRef other_global = nullptr; + if (ec.e->kind == Entity_Variable) { + other_global = LLVMGetNamedGlobal(ec.other_module->mod, ec.cname); + if (other_global) { + LLVMSetLinkage(other_global, LLVMWeakAnyLinkage); + } + } else if (ec.e->kind == Entity_Procedure) { + other_global = LLVMGetNamedFunction(ec.other_module->mod, ec.cname); + if (other_global) { + LLVMSetLinkage(other_global, LLVMWeakAnyLinkage); + } + } } } + gb_internal void lb_emit_init_context(lbProcedure *p, lbAddr addr) { TEMPORARY_ALLOCATOR_GUARD(); @@ -969,14 +976,16 @@ gb_internal lbValue lb_const_hash(lbModule *m, lbValue key, Type *key_type) { gb_internal lbValue lb_gen_map_key_hash(lbProcedure *p, lbValue const &map_ptr, lbValue key, lbValue *key_ptr_) { TEMPORARY_ALLOCATOR_GUARD(); - lbValue key_ptr = lb_address_from_load_or_generate_local(p, key); + Type* key_type = base_type(type_deref(map_ptr.type))->Map.key; + + lbValue real_key = lb_emit_conv(p, key, key_type); + + lbValue key_ptr = lb_address_from_load_or_generate_local(p, real_key); key_ptr = lb_emit_conv(p, key_ptr, t_rawptr); if (key_ptr_) *key_ptr_ = key_ptr; - Type* key_type = base_type(type_deref(map_ptr.type))->Map.key; - - lbValue hashed_key = lb_const_hash(p->module, key, key_type); + lbValue hashed_key = lb_const_hash(p->module, real_key, key_type); if (hashed_key.value == nullptr) { lbValue hasher = lb_hasher_proc_for_type(p->module, key_type); @@ -1321,6 +1330,24 @@ gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { return 0; } +gb_internal GB_COMPARE_PROC(llvm_global_entity_cmp) { + Entity *x = *cast(Entity **)a; + Entity *y = *cast(Entity **)b; + if (x == y) { + return 0; + } + if (x->kind != y->kind) { + return cast(i32)(x->kind - y->kind); + } + + i32 cmp = 0; + cmp = token_pos_cmp(x->token.pos, y->token.pos); + if (!cmp) { + return cmp; + } + return cmp; +} + gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, CheckerInfo *info, bool do_threading) { auto *min_dep_set = &info->minimum_dependency_set; @@ -1369,6 +1396,7 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); } + GB_ASSERT(m != nullptr); if (e->kind == Entity_Procedure) { array_add(&m->global_procedures_to_create, e); @@ -1377,6 +1405,12 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker } } + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + array_sort(m->global_types_to_create, llvm_global_entity_cmp); + array_sort(m->global_procedures_to_create, llvm_global_entity_cmp); + } + if (do_threading) { for (auto const &entry : gen->modules) { lbModule *m = entry.value; @@ -1408,7 +1442,9 @@ gb_internal bool lb_is_module_empty(lbModule *m) { } for (auto g = LLVMGetFirstGlobal(m->mod); g != nullptr; g = LLVMGetNextGlobal(g)) { - if (LLVMGetLinkage(g) == LLVMExternalLinkage) { + LLVMLinkage linkage = LLVMGetLinkage(g); + if (linkage == LLVMExternalLinkage || + linkage == LLVMWeakAnyLinkage) { continue; } if (!LLVMIsExternallyInitialized(g)) { @@ -1462,10 +1498,6 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { lb_populate_function_pass_manager(m, m->function_pass_managers[lbFunctionPassManager_default], false, build_context.optimization_level); lb_populate_function_pass_manager(m, m->function_pass_managers[lbFunctionPassManager_default_without_memcpy], true, build_context.optimization_level); lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_none], -1); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_minimal], 0); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_size], 1); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_speed], 2); - lb_populate_function_pass_manager_specific(m, m->function_pass_managers[lbFunctionPassManager_aggressive], 3); for (i32 i = 0; i < lbFunctionPassManager_COUNT; i++) { LLVMFinalizeFunctionPassManager(m->function_pass_managers[i]); @@ -1489,15 +1521,12 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { if (p->entity && p->entity->kind == Entity_Procedure) { switch (p->entity->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: - case ProcedureOptimizationMode_Minimal: - pass_manager_kind = lbFunctionPassManager_minimal; + pass_manager_kind = lbFunctionPassManager_none; + GB_ASSERT(lb_proc_has_attribute(p->module, p->value, "optnone")); + GB_ASSERT(lb_proc_has_attribute(p->module, p->value, "noinline")); break; - case ProcedureOptimizationMode_Size: - pass_manager_kind = lbFunctionPassManager_size; - lb_add_attribute_to_proc(p->module, p->value, "optsize"); - break; - case ProcedureOptimizationMode_Speed: - pass_manager_kind = lbFunctionPassManager_speed; + case ProcedureOptimizationMode_FavorSize: + GB_ASSERT(lb_proc_has_attribute(p->module, p->value, "optsize")); break; } } @@ -1553,6 +1582,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { switch (build_context.optimization_level) { case -1: + array_add(&passes, "function(annotation-remarks)"); break; case 0: array_add(&passes, "always-inline"); @@ -2532,20 +2562,37 @@ gb_internal String lb_filepath_ll_for_module(lbModule *m) { return path; } + gb_internal String lb_filepath_obj_for_module(lbModule *m) { - String path = concatenate3_strings(permanent_allocator(), - build_context.build_paths[BuildPath_Output].basename, - STR_LIT("/"), - build_context.build_paths[BuildPath_Output].name - ); + String basename = build_context.build_paths[BuildPath_Output].basename; + String name = build_context.build_paths[BuildPath_Output].name; + + bool use_temporary_directory = false; + if (USE_SEPARATE_MODULES && build_context.build_mode == BuildMode_Executable) { + // NOTE(bill): use a temporary directory + String dir = temporary_directory(permanent_allocator()); + if (dir.len != 0) { + basename = dir; + use_temporary_directory = true; + } + } + + gbString path = gb_string_make_length(heap_allocator(), basename.text, basename.len); + path = gb_string_appendc(path, "/"); + path = gb_string_append_length(path, name.text, name.len); if (m->file) { char buf[32] = {}; isize n = gb_snprintf(buf, gb_size_of(buf), "-%u", m->file->id); String suffix = make_string((u8 *)buf, n-1); - path = concatenate_strings(permanent_allocator(), path, suffix); + path = gb_string_append_length(path, suffix.text, suffix.len); } else if (m->pkg) { - path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); + path = gb_string_appendc(path, "-"); + path = gb_string_append_length(path, m->pkg->name.text, m->pkg->name.len); + } + + if (use_temporary_directory) { + path = gb_string_append_fmt(path, "-%p", m); } String ext = {}; @@ -2583,7 +2630,10 @@ gb_internal String lb_filepath_obj_for_module(lbModule *m) { } } - return concatenate_strings(permanent_allocator(), path, ext); + path = gb_string_append_length(path, ext.text, ext.len); + + return make_string(cast(u8 *)path, gb_string_length(path)); + } @@ -2642,7 +2692,6 @@ gb_internal bool lb_llvm_object_generation(lbGenerator *gen, bool do_threading) String filepath_ll = lb_filepath_ll_for_module(m); String filepath_obj = lb_filepath_obj_for_module(m); - // gb_printf_err("%.*s\n", LIT(filepath_obj)); array_add(&gen->output_object_paths, filepath_obj); array_add(&gen->output_temp_paths, filepath_ll); @@ -3224,7 +3273,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetLinkage(g.value, LLVMDLLExportLinkage); LLVMSetDLLStorageClass(g.value, LLVMDLLExportStorageClass); } else if (!is_foreign) { - LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage); + LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMWeakAnyLinkage : LLVMInternalLinkage); } lb_set_linkage_from_entity_flags(m, g.value, e->flags); @@ -3241,11 +3290,12 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { if (!is_type_any(e->type) && !is_type_union(e->type)) { if (tav.mode != Addressing_Invalid) { if (tav.value.kind != ExactValue_Invalid) { + bool is_rodata = e->kind == Entity_Variable && e->Variable.is_rodata; ExactValue v = tav.value; - lbValue init = lb_const_value(m, tav.type, v); + lbValue init = lb_const_value(m, tav.type, v, false, is_rodata); LLVMSetInitializer(g.value, init.value); var.is_initialized = true; - if (e->kind == Entity_Variable && e->Variable.is_rodata) { + if (is_rodata) { LLVMSetGlobalConstant(g.value, true); } } @@ -3394,7 +3444,22 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Add Foreign Library Paths"); lb_add_foreign_library_paths(gen); - TIME_SECTION("LLVM Object Generation"); + TIME_SECTION("LLVM Correct Entity Linkage"); + lb_correct_entity_linkage(gen); + + //////////////////////////////////////////// + for (auto const &entry: gen->modules) { + lbModule *m = entry.value; + if (!lb_is_module_empty(m)) { + gen->used_module_count += 1; + } + } + + gbString label_object_generation = gb_string_make(heap_allocator(), "LLVM Object Generation"); + if (gen->used_module_count > 1) { + label_object_generation = gb_string_append_fmt(label_object_generation, " (%td used modules)", gen->used_module_count); + } + TIME_SECTION_WITH_LEN(label_object_generation, gb_string_length(label_object_generation)); if (build_context.ignore_llvm_build) { gb_printf_err("LLVM object generation has been ignored!\n"); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 447e93d42..02daecf6b 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -134,11 +134,6 @@ enum lbFunctionPassManagerKind { lbFunctionPassManager_default, lbFunctionPassManager_default_without_memcpy, lbFunctionPassManager_none, - lbFunctionPassManager_minimal, - lbFunctionPassManager_size, - lbFunctionPassManager_speed, - lbFunctionPassManager_aggressive, - lbFunctionPassManager_COUNT }; @@ -152,6 +147,7 @@ struct lbModule { CheckerInfo *info; AstPackage *pkg; // possibly associated AstFile *file; // possibly associated + char const *module_name; PtrMap types; // mutex: types_mutex PtrMap struct_field_remapping; // Key: LLVMTypeRef or Type *, mutex: types_mutex @@ -205,6 +201,12 @@ struct lbModule { LLVMPassManagerRef function_pass_managers[lbFunctionPassManager_COUNT]; }; +struct lbEntityCorrection { + lbModule * other_module; + Entity * e; + char const *cname; +}; + struct lbGenerator : LinkerData { CheckerInfo *info; @@ -218,9 +220,13 @@ struct lbGenerator : LinkerData { std::atomic global_array_index; std::atomic global_generated_index; + isize used_module_count; + lbProcedure *startup_runtime; lbProcedure *cleanup_runtime; lbProcedure *objc_names; + + MPSCQueue entities_to_correct_linkage; }; @@ -299,6 +305,11 @@ enum lbProcedureFlag : u32 { lbProcedureFlag_DebugAllocaCopy = 1<<1, }; +struct lbVariadicReuseSlices { + Type *slice_type; + lbAddr slice_addr; +}; + struct lbProcedure { u32 flags; u16 state_flags; @@ -339,8 +350,10 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; - LLVMValueRef temp_callee_return_struct_memory; + Array variadic_reuses; + lbAddr variadic_reuse_base_array_ptr; + LLVMValueRef temp_callee_return_struct_memory; Ast *curr_stmt; Array scope_stack; @@ -367,7 +380,7 @@ struct lbProcedure { gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c); -gb_internal String lb_mangle_name(lbModule *m, Entity *e); +gb_internal String lb_mangle_name(Entity *e); gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String name = {}); gb_internal LLVMAttributeRef lb_create_enum_attribute(LLVMContextRef ctx, char const *name, u64 value=0); @@ -385,7 +398,7 @@ gb_internal lbBlock *lb_create_block(lbProcedure *p, char const *name, bool appe gb_internal lbValue lb_const_nil(lbModule *m, Type *type); gb_internal lbValue lb_const_undef(lbModule *m, Type *type); -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local=true); +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local=true, bool is_rodata=false); gb_internal lbValue lb_const_bool(lbModule *m, Type *type, bool value); gb_internal lbValue lb_const_int(lbModule *m, Type *type, u64 value); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 3ed8d72d9..6a6b119aa 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -94,9 +94,6 @@ gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst) { LLVMTypeKind kind = LLVMGetTypeKind(dst); switch (kind) { case LLVMPointerTypeKind: - if (LB_USE_NEW_PASS_SYSTEM) { - return val; - } return LLVMConstPointerCast(val, dst); case LLVMStructTypeKind: // GB_PANIC("%s -> %s", LLVMPrintValueToString(val), LLVMPrintTypeToString(dst)); @@ -341,6 +338,15 @@ gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, S return addr.addr; } +gb_internal lbValue lb_const_source_code_location_as_global_ptr(lbModule *m, String const &procedure, TokenPos const &pos) { + lbValue loc = lb_const_source_code_location_const(m, procedure, pos); + lbAddr addr = lb_add_global_generated(m, loc.type, loc, nullptr); + lb_make_global_private_const(addr); + return addr.addr; +} + + + gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, Ast *node) { lbValue loc = lb_emit_source_code_location_const(p, node); @@ -359,7 +365,11 @@ gb_internal lbValue lb_emit_source_code_location_as_global(lbProcedure *p, Ast * -gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, bool allow_local) { +gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, bool allow_local, bool is_rodata) { + if (allow_local) { + is_rodata = false; + } + bool is_local = allow_local && m->curr_procedure != nullptr; bool is_const = true; if (is_local) { @@ -428,6 +438,8 @@ gb_internal LLVMValueRef lb_big_int_to_llvm(lbModule *m, Type *original_type, Bi } } + GB_ASSERT(!is_type_array(original_type)); + LLVMValueRef value = LLVMConstIntOfArbitraryPrecision(lb_type(m, original_type), cast(unsigned)((sz+7)/8), cast(u64 *)rop); if (big_int_is_neg(a)) { value = LLVMConstNeg(value); @@ -462,7 +474,11 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, return lb_is_elem_const(elem, ft); } -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local) { +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local, bool is_rodata) { + if (allow_local) { + is_rodata = false; + } + LLVMContextRef ctx = m->ctx; type = default_type(type); @@ -520,7 +536,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo count = gb_max(cast(isize)cl->max_count, count); Type *elem = base_type(type)->Slice.elem; Type *t = alloc_type_array(elem, count); - lbValue backing_array = lb_const_value(m, t, value, allow_local); + lbValue backing_array = lb_const_value(m, t, value, allow_local, is_rodata); LLVMValueRef array_data = nullptr; @@ -557,6 +573,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo array_data = LLVMAddGlobal(m->mod, lb_type(m, t), str); LLVMSetInitializer(array_data, backing_array.value); + if (is_rodata) { + LLVMSetGlobalConstant(array_data, true); + } + lbValue g = {}; g.value = array_data; g.type = t; @@ -604,7 +624,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } // NOTE(bill, 2021-10-07): Allow for array programming value constants Type *core_elem = core_array_type(type); - return lb_const_value(m, core_elem, value, allow_local); + return lb_const_value(m, core_elem, value, allow_local, is_rodata); } else if (is_type_u8_array(type) && value.kind == ExactValue_String) { GB_ASSERT(type->Array.count == value.value_string.len); LLVMValueRef data = LLVMConstStringInContext(ctx, @@ -622,7 +642,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo Type *elem = type->Array.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, cast(isize)count); for (i64 i = 0; i < count; i++) { @@ -640,7 +660,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo Type *elem = type->Matrix.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); i64 total_elem_count = matrix_type_total_internal_elems(type); @@ -662,7 +682,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 count = type->SimdVector.count; Type *elem = type->SimdVector.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, count); @@ -791,7 +811,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo case ExactValue_Compound: if (is_type_slice(type)) { - return lb_const_value(m, type, value, allow_local); + return lb_const_value(m, type, value, allow_local, is_rodata); } else if (is_type_array(type)) { ast_node(cl, CompoundLit, value.value_compound); Type *elem_type = type->Array.elem; @@ -825,7 +845,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -840,7 +860,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -853,7 +873,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == type->Array.count, "%td != %td", elem_count, type->Array.count); @@ -863,13 +883,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = elem_count; i < type->Array.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata); return res; } } else if (is_type_enumerated_array(type)) { @@ -909,7 +929,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -924,7 +944,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -937,7 +957,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == type->EnumeratedArray.count, "%td != %td", elem_count, type->EnumeratedArray.count); @@ -947,13 +967,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = elem_count; i < type->EnumeratedArray.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata); return res; } } else if (is_type_simd_vector(type)) { @@ -992,7 +1012,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1007,7 +1027,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -1026,7 +1046,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } LLVMTypeRef et = lb_type(m, elem_type); @@ -1075,7 +1095,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { - values[index] = lb_const_value(m, f->type, tav.value, allow_local).value; + values[index] = lb_const_value(m, f->type, tav.value, allow_local, is_rodata).value; visited[index] = true; } else { if (!visited[index]) { @@ -1117,7 +1137,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } if (is_constant) { - LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local).value; + LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local, is_rodata).value; if (LLVMIsConstant(elem_value)) { values[index] = llvm_const_insert_value(m, values[index], elem_value, idx_list, idx_list_len); } else { @@ -1139,7 +1159,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { - values[index] = lb_const_value(m, f->type, val, allow_local).value; + values[index] = lb_const_value(m, f->type, val, allow_local, is_rodata).value; visited[index] = true; } } @@ -1265,7 +1285,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { i64 offset = matrix_row_major_index_to_offset(type, k); GB_ASSERT(values[offset] == nullptr); @@ -1277,7 +1297,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); GB_ASSERT(index < max_count); TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; i64 offset = matrix_row_major_index_to_offset(type, index); GB_ASSERT(values[offset] == nullptr); values[offset] = val; @@ -1290,7 +1310,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == max_count, "%td != %td", elem_count, max_count); @@ -1301,7 +1321,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo GB_ASSERT(tav.mode != Addressing_Invalid); i64 offset = 0; offset = matrix_row_major_index_to_offset(type, i); - values[offset] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[offset] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = 0; i < total_count; i++) { if (values[i] == nullptr) { @@ -1309,7 +1329,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata); return res; } } else { diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index f1ace5f06..c896f889d 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -1187,6 +1187,7 @@ gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); } + GB_ASSERT(m != nullptr); if (is_type_integer(e->type)) { ExactValue const &value = e->Constant.value; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index bcacc0537..1f0719e13 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -296,12 +296,6 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu GB_ASSERT(vector_type0 == vector_type1); LLVMTypeRef vector_type = vector_type0; - LLVMValueRef lhs_vp = LLVMBuildPointerCast(p->builder, lhs_ptr.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef rhs_vp = LLVMBuildPointerCast(p->builder, rhs_ptr.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef x = LLVMBuildLoad2(p->builder, vector_type, lhs_vp, ""); - LLVMValueRef y = LLVMBuildLoad2(p->builder, vector_type, rhs_vp, ""); - LLVMValueRef z = nullptr; - Type *integral_type = base_type(elem_type); if (is_type_simd_vector(integral_type)) { integral_type = core_array_type(integral_type); @@ -311,8 +305,18 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu case Token_Add: op = Token_Or; break; case Token_Sub: op = Token_AndNot; break; } + Type *u = bit_set_to_int(type); + if (is_type_array(u)) { + return false; + } } + LLVMValueRef lhs_vp = LLVMBuildPointerCast(p->builder, lhs_ptr.value, LLVMPointerType(vector_type, 0), ""); + LLVMValueRef rhs_vp = LLVMBuildPointerCast(p->builder, rhs_ptr.value, LLVMPointerType(vector_type, 0), ""); + LLVMValueRef x = LLVMBuildLoad2(p->builder, vector_type, lhs_vp, ""); + LLVMValueRef y = LLVMBuildLoad2(p->builder, vector_type, rhs_vp, ""); + LLVMValueRef z = nullptr; + if (is_type_float(integral_type)) { switch (op) { case Token_Add: @@ -1286,6 +1290,14 @@ handle_op:; case Token_Add: op = Token_Or; break; case Token_Sub: op = Token_AndNot; break; } + Type *u = bit_set_to_int(type); + if (is_type_array(u)) { + lhs.type = u; + rhs.type = u; + res = lb_emit_arith(p, op, lhs, rhs, u); + res.type = type; + return res; + } } Type *integral_type = type; @@ -1441,6 +1453,7 @@ gb_internal lbValue lb_build_binary_in(lbProcedure *p, lbValue left, lbValue rig GB_ASSERT(are_types_identical(left.type, key_type)); Type *it = bit_set_to_int(rt); + left = lb_emit_conv(p, left, it); if (is_type_different_to_arch_endianness(it)) { left = lb_emit_byte_swap(p, left, integer_endian_type_to_platform_type(it)); @@ -2054,6 +2067,26 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } } + // bit_set <-> backing type + if (is_type_bit_set(src)) { + Type *backing = bit_set_to_int(src); + if (are_types_identical(backing, dst)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + if (is_type_bit_set(dst)) { + Type *backing = bit_set_to_int(dst); + if (are_types_identical(src, backing)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + // Pointer <-> uintptr if (is_type_pointer(src) && is_type_uintptr(dst)) { @@ -2491,9 +2524,16 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left if (are_types_identical(a, b)) { // NOTE(bill): No need for a conversion } else if (lb_is_const(left) || lb_is_const_nil(left)) { + if (lb_is_const_nil(left)) { + return lb_emit_comp_against_nil(p, op_kind, right); + } left = lb_emit_conv(p, left, right.type); } else if (lb_is_const(right) || lb_is_const_nil(right)) { + if (lb_is_const_nil(right)) { + return lb_emit_comp_against_nil(p, op_kind, left); + } right = lb_emit_conv(p, right, left.type); + } else { Type *lt = left.type; Type *rt = right.type; @@ -2951,13 +2991,32 @@ gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, case Type_Pointer: case Type_MultiPointer: case Type_Proc: - case Type_BitSet: if (op_kind == Token_CmpEq) { res.value = LLVMBuildIsNull(p->builder, x.value, ""); } else if (op_kind == Token_NotEq) { res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); } return res; + case Type_BitSet: + { + Type *u = bit_set_to_int(bt); + if (is_type_array(u)) { + auto args = array_make(permanent_allocator(), 2); + lbValue lhs = lb_address_from_load_or_generate_local(p, x); + args[0] = lb_emit_conv(p, lhs, t_rawptr); + args[1] = lb_const_int(p->module, t_int, type_size_of(t)); + lbValue val = lb_emit_runtime_call(p, "memory_compare_zero", args); + lbValue res = lb_emit_comp(p, op_kind, val, lb_const_int(p->module, t_int, 0)); + return res; + } else { + if (op_kind == Token_CmpEq) { + res.value = LLVMBuildIsNull(p->builder, x.value, ""); + } else if (op_kind == Token_NotEq) { + res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); + } + } + return res; + } case Type_Slice: { @@ -4878,29 +4937,43 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { case Type_BitSet: { i64 sz = type_size_of(type); if (cl->elems.count > 0 && sz > 0) { - lb_addr_store(p, v, lb_const_value(p->module, type, exact_value_compound(expr))); - lbValue lower = lb_const_value(p->module, t_int, exact_value_i64(bt->BitSet.lower)); - for (Ast *elem : cl->elems) { - GB_ASSERT(elem->kind != Ast_FieldValue); - if (lb_is_elem_const(elem, et)) { - continue; + Type *backing = bit_set_to_int(type); + if (is_type_array(backing)) { + GB_PANIC("TODO: bit_set [N]T"); + Type *base_it = core_array_type(backing); + i64 bits_per_elem = 8*type_size_of(base_it); + gb_unused(bits_per_elem); + lbValue one = lb_const_value(p->module, t_i64, exact_value_i64(1)); + for (Ast *elem : cl->elems) { + GB_ASSERT(elem->kind != Ast_FieldValue); + lbValue expr = lb_build_expr(p, elem); + GB_ASSERT(expr.type->kind != Type_Tuple); + + lbValue e = lb_emit_conv(p, expr, t_i64); + e = lb_emit_arith(p, Token_Sub, e, lower, t_i64); + // lbValue idx = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); + // lbValue val = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); } - - lbValue expr = lb_build_expr(p, elem); - GB_ASSERT(expr.type->kind != Type_Tuple); - + } else { Type *it = bit_set_to_int(bt); lbValue one = lb_const_value(p->module, it, exact_value_i64(1)); - lbValue e = lb_emit_conv(p, expr, it); - e = lb_emit_arith(p, Token_Sub, e, lower, it); - e = lb_emit_arith(p, Token_Shl, one, e, it); + for (Ast *elem : cl->elems) { + GB_ASSERT(elem->kind != Ast_FieldValue); - lbValue old_value = lb_emit_transmute(p, lb_addr_load(p, v), it); - lbValue new_value = lb_emit_arith(p, Token_Or, old_value, e, it); - new_value = lb_emit_transmute(p, new_value, type); - lb_addr_store(p, v, new_value); + lbValue expr = lb_build_expr(p, elem); + GB_ASSERT(expr.type->kind != Type_Tuple); + + lbValue e = lb_emit_conv(p, expr, it); + e = lb_emit_arith(p, Token_Sub, e, lower, it); + e = lb_emit_arith(p, Token_Shl, one, e, it); + + lbValue old_value = lb_emit_transmute(p, lb_addr_load(p, v), it); + lbValue new_value = lb_emit_arith(p, Token_Or, old_value, e, it); + new_value = lb_emit_transmute(p, new_value, type); + lb_addr_store(p, v, new_value); + } } } break; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index c2ae5de74..a91c1d1fe 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -29,8 +29,9 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { module_name = gb_string_appendc(module_name, "-builtin"); } + m->module_name = module_name ? module_name : "odin_package"; m->ctx = LLVMContextCreate(); - m->mod = LLVMModuleCreateWithNameInContext(module_name ? module_name : "odin_package", m->ctx); + m->mod = LLVMModuleCreateWithNameInContext(m->module_name, m->ctx); // m->debug_builder = nullptr; if (build_context.ODIN_DEBUG) { enum {DEBUG_METADATA_VERSION = 3}; @@ -71,7 +72,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { map_init(&m->hasher_procs); map_init(&m->map_get_procs); map_init(&m->map_set_procs); - if (build_context.use_separate_modules) { + if (USE_SEPARATE_MODULES) { array_init(&m->procedures_to_generate, a, 0, 1<<10); map_init(&m->procedure_values, 1<<11); } else { @@ -118,15 +119,17 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_init(&gen->anonymous_proc_lits, 1024); if (USE_SEPARATE_MODULES) { + bool module_per_file = build_context.module_per_file && build_context.optimization_level <= 0; for (auto const &entry : gen->info->packages) { AstPackage *pkg = entry.value; - #if 1 auto m = gb_alloc_item(permanent_allocator(), lbModule); m->pkg = pkg; m->gen = gen; map_set(&gen->modules, cast(void *)pkg, m); lb_init_module(m, c); - #else + if (!module_per_file) { + continue; + } // NOTE(bill): Probably per file is not a good idea, so leave this for later for (AstFile *file : pkg->files) { auto m = gb_alloc_item(permanent_allocator(), lbModule); @@ -136,7 +139,6 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_set(&gen->modules, cast(void *)file, m); lb_init_module(m, c); } - #endif } } @@ -144,13 +146,14 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_set(&gen->modules, cast(void *)1, &gen->default_module); lb_init_module(&gen->default_module, c); - for (auto const &entry : gen->modules) { lbModule *m = entry.value; LLVMContextRef ctx = LLVMGetModuleContext(m->mod); map_set(&gen->modules_through_ctx, ctx, m); } + mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); + return true; } @@ -387,12 +390,14 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { if (e->file) { found = map_get(&gen->modules, cast(void *)e->file); if (found) { + GB_ASSERT(*found != nullptr); return *found; } } if (e->pkg) { found = map_get(&gen->modules, cast(void *)e->pkg); if (found) { + GB_ASSERT(*found != nullptr); return *found; } } @@ -1011,13 +1016,15 @@ gb_internal void lb_emit_store(lbProcedure *p, lbValue ptr, lbValue value) { return; } - Type *a = type_deref(ptr.type); + Type *a = type_deref(ptr.type, true); if (LLVMIsNull(value.value)) { LLVMTypeRef src_t = llvm_addr_type(p->module, ptr); if (is_type_proc(a)) { LLVMTypeRef rawptr_type = lb_type(p->module, t_rawptr); LLVMTypeRef rawptr_ptr_type = LLVMPointerType(rawptr_type, 0); LLVMBuildStore(p->builder, LLVMConstNull(rawptr_type), LLVMBuildBitCast(p->builder, ptr.value, rawptr_ptr_type, "")); + } else if (is_type_bit_set(a)) { + lb_mem_zero_ptr(p, ptr.value, a, 1); } else if (lb_sizeof(src_t) <= lb_max_zero_init_size()) { LLVMBuildStore(p->builder, LLVMConstNull(src_t), ptr.value); } else { @@ -1105,13 +1112,17 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { return lb_addr_load(p, addr); } - GB_ASSERT(is_type_pointer(value.type)); + GB_ASSERT_MSG(is_type_pointer(value.type), "%s", type_to_string(value.type)); Type *t = type_deref(value.type); LLVMValueRef v = LLVMBuildLoad2(p->builder, lb_type(p->module, t), value.value, ""); - u64 is_packed = lb_get_metadata_custom_u64(p->module, value.value, ODIN_METADATA_IS_PACKED); - if (is_packed != 0) { - LLVMSetAlignment(v, 1); + // If it is not an instruction it isn't a GEP, so we don't need to track alignment in the metadata, + // which is not possible anyway (only LLVM instructions can have metadata). + if (LLVMIsAInstruction(value.value)) { + u64 is_packed = lb_get_metadata_custom_u64(p->module, value.value, ODIN_METADATA_IS_PACKED); + if (is_packed != 0) { + LLVMSetAlignment(v, 1); + } } return lbValue{v, t}; @@ -1159,11 +1170,12 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { r = lb_addr_load(p, dst); r.value = LLVMBuildShl(p->builder, r.value, shift_amount, ""); } else if ((addr.bitfield.bit_offset % 8) == 0) { + do_mask = 8*dst_byte_size != addr.bitfield.bit_size; + lbValue copy_size = byte_size; lbValue src_offset = lb_emit_conv(p, src, t_u8_ptr); src_offset = lb_emit_ptr_offset(p, src_offset, byte_offset); if (addr.bitfield.bit_offset + 8*dst_byte_size <= total_bitfield_bit_size) { - do_mask = true; copy_size = lb_const_int(p->module, t_uintptr, dst_byte_size); } lb_mem_copy_non_overlapping(p, dst.addr, src_offset, copy_size, false); @@ -1525,7 +1537,7 @@ gb_internal void lb_clone_struct_type(LLVMTypeRef dst, LLVMTypeRef src) { LLVMStructSetBody(dst, fields, field_count, LLVMIsPackedStruct(src)); } -gb_internal String lb_mangle_name(lbModule *m, Entity *e) { +gb_internal String lb_mangle_name(Entity *e) { String name = e->token.string; AstPackage *pkg = e->pkg; @@ -1625,6 +1637,7 @@ gb_internal String lb_set_nested_type_name_ir_mangled_name(Entity *e, lbProcedur } gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String default_name) { + GB_ASSERT(m != nullptr); if (e != nullptr && e->kind == Entity_TypeName && e->TypeName.ir_mangled_name.len != 0) { return e->TypeName.ir_mangled_name; } @@ -1656,7 +1669,7 @@ gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String default_nam } if (!no_name_mangle) { - name = lb_mangle_name(m, e); + name = lb_mangle_name(e); } if (name.len == 0) { name = e->token.string; @@ -2519,6 +2532,12 @@ gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, cha gb_internal void lb_add_attribute_to_proc(lbModule *m, LLVMValueRef proc_value, char const *name, u64 value=0) { LLVMAddAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(m->ctx, name, value)); } + +gb_internal bool lb_proc_has_attribute(lbModule *m, LLVMValueRef proc_value, char const *name) { + LLVMAttributeRef ref = LLVMGetEnumAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, LLVMGetEnumAttributeKindForName(name, gb_strlen(name))); + return ref != nullptr; +} + gb_internal void lb_add_attribute_to_proc_with_string(lbModule *m, LLVMValueRef proc_value, String const &name, String const &value) { LLVMAttributeRef attr = lb_create_string_attribute(m->ctx, name, value); LLVMAddAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, attr); @@ -3034,7 +3053,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { if (e->code_gen_module != nullptr) { other_module = e->code_gen_module; } else { - other_module = nullptr; + other_module = &m->gen->default_module; } is_external = other_module != m; } @@ -3052,8 +3071,6 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); - // LLVMSetLinkage(other_g.value, LLVMExternalLinkage); - if (e->Variable.thread_local_model != "") { LLVMSetThreadLocal(g.value, true); @@ -3077,7 +3094,9 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { return g; } } - GB_PANIC("\n\tError in: %s, missing value '%.*s'\n", token_pos_to_string(e->token.pos), LIT(e->token.string)); + + GB_PANIC("\n\tError in: %s, missing value '%.*s' in module %s\n", + token_pos_to_string(e->token.pos), LIT(e->token.string), m->module_name); return {}; } diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index 6a6d2f802..7fe1359b4 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -396,18 +396,22 @@ gb_internal LLVMValueRef lb_run_instrumentation_pass_insert_call(lbProcedure *p, lbValue cc = lb_find_procedure_value_from_entity(m, entity); LLVMValueRef args[3] = {}; - args[0] = p->value; + args[0] = LLVMConstPointerCast(p->value, lb_type(m, t_rawptr)); - LLVMValueRef returnaddress_args[1] = {}; + if (is_arch_wasm()) { + args[1] = LLVMConstPointerNull(lb_type(m, t_rawptr)); + } else { + LLVMValueRef returnaddress_args[1] = {}; - returnaddress_args[0] = LLVMConstInt(LLVMInt32TypeInContext(m->ctx), 0, false); + returnaddress_args[0] = LLVMConstInt(LLVMInt32TypeInContext(m->ctx), 0, false); - char const *instrinsic_name = "llvm.returnaddress"; - unsigned id = LLVMLookupIntrinsicID(instrinsic_name, gb_strlen(instrinsic_name)); - GB_ASSERT_MSG(id != 0, "Unable to find %s", instrinsic_name); - LLVMValueRef ip = LLVMGetIntrinsicDeclaration(m->mod, id, nullptr, 0); - LLVMTypeRef call_type = LLVMIntrinsicGetType(m->ctx, id, nullptr, 0); - args[1] = LLVMBuildCall2(dummy_builder, call_type, ip, returnaddress_args, gb_count_of(returnaddress_args), ""); + char const *instrinsic_name = "llvm.returnaddress"; + unsigned id = LLVMLookupIntrinsicID(instrinsic_name, gb_strlen(instrinsic_name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", instrinsic_name); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(m->mod, id, nullptr, 0); + LLVMTypeRef call_type = LLVMIntrinsicGetType(m->ctx, id, nullptr, 0); + args[1] = LLVMBuildCall2(dummy_builder, call_type, ip, returnaddress_args, gb_count_of(returnaddress_args), ""); + } Token name = {}; if (p->entity) { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 68ba4f74e..2f736ff6c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -159,20 +159,19 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i case ProcInlining_no_inline: lb_add_attribute_to_proc(m, p->value, "noinline"); break; + default: + if (build_context.internal_no_inline) { + lb_add_attribute_to_proc(m, p->value, "noinline"); + break; + } } switch (entity->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: - break; - case ProcedureOptimizationMode_Minimal: lb_add_attribute_to_proc(m, p->value, "optnone"); lb_add_attribute_to_proc(m, p->value, "noinline"); break; - case ProcedureOptimizationMode_Size: - lb_add_attribute_to_proc(m, p->value, "optsize"); - break; - case ProcedureOptimizationMode_Speed: - // TODO(bill): handle this correctly + case ProcedureOptimizationMode_FavorSize: lb_add_attribute_to_proc(m, p->value, "optsize"); break; } @@ -259,6 +258,11 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i if (e->flags&EntityFlag_NoAlias) { lb_add_proc_attribute_at_index(p, offset+parameter_index, "noalias"); } + if (e->flags&EntityFlag_NoCapture) { + if (is_type_internally_pointer_like(e->type)) { + lb_add_proc_attribute_at_index(p, offset+parameter_index, "nocapture"); + } + } parameter_index += 1; } } @@ -523,6 +527,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lb_start_block(p, p->entry_block); map_init(&p->direct_parameters); + p->variadic_reuses.allocator = heap_allocator(); GB_ASSERT(p->type != nullptr); @@ -2280,6 +2285,39 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return res; } + case BuiltinProc_add_sat: + case BuiltinProc_sub_sat: + { + Type *main_type = tv.type; + Type *type = main_type; + + lbValue x = lb_build_expr(p, ce->args[0]); + lbValue y = lb_build_expr(p, ce->args[1]); + x = lb_emit_conv(p, x, type); + y = lb_emit_conv(p, y, type); + + char const *name = nullptr; + if (is_type_unsigned(type)) { + switch (id) { + case BuiltinProc_add_sat: name = "llvm.uadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.usub.sat"; break; + } + } else { + switch (id) { + case BuiltinProc_add_sat: name = "llvm.sadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.ssub.sat"; break; + } + } + LLVMTypeRef types[1] = {lb_type(p->module, type)}; + + LLVMValueRef args[2] = { x.value, y.value }; + + lbValue res = {}; + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + res.type = type; + return res; + } + case BuiltinProc_sqrt: { Type *type = tv.type; @@ -3456,17 +3494,67 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } isize slice_len = var_args.count; if (slice_len > 0) { - lbAddr slice = lb_add_local_generated(p, slice_type, true); - lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); + lbAddr slice = {}; + + for (auto const &vr : p->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + slice = vr.slice_addr; + break; + } + } + + DeclInfo *d = decl_info_of_entity(p->entity); + if (d != nullptr && slice.addr.value == nullptr) { + for (auto const &vr : d->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + #if LLVM_VERSION_MAJOR >= 13 + // NOTE(bill): No point wasting even more memory, just reuse this stack variable too + if (p->variadic_reuses.count > 0) { + slice = p->variadic_reuses[0].slice_addr; + } else { + slice = lb_add_local_generated(p, slice_type, true); + } + // NOTE(bill): Change the underlying type to match the specific type + slice.addr.type = alloc_type_pointer(slice_type); + #else + slice = lb_add_local_generated(p, slice_type, true); + #endif + array_add(&p->variadic_reuses, lbVariadicReuseSlices{slice_type, slice}); + break; + } + } + } + + lbValue base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + if (base_array_ptr.value == nullptr) { + if (d != nullptr) { + i64 max_bytes = d->variadic_reuse_max_bytes; + i64 max_align = gb_max(d->variadic_reuse_max_align, 16); + p->variadic_reuse_base_array_ptr = lb_add_local_generated(p, alloc_type_array(t_u8, max_bytes), true); + lb_try_update_alignment(p->variadic_reuse_base_array_ptr.addr, cast(unsigned)max_align); + base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + } else { + base_array_ptr = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true).addr; + } + } + + if (slice.addr.value == nullptr) { + slice = lb_add_local_generated(p, slice_type, true); + } + + GB_ASSERT(base_array_ptr.value != nullptr); + GB_ASSERT(slice.addr.value != nullptr); + + base_array_ptr = lb_emit_conv(p, base_array_ptr, alloc_type_pointer(alloc_type_array(elem_type, slice_len))); for (isize i = 0; i < var_args.count; i++) { - lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); + lbValue addr = lb_emit_array_epi(p, base_array_ptr, cast(i32)i); lbValue var_arg = var_args[i]; var_arg = lb_emit_conv(p, var_arg, elem_type); lb_emit_store(p, addr, var_arg); } - lbValue base_elem = lb_emit_array_epi(p, base_array.addr, 0); + lbValue base_elem = lb_emit_array_epi(p, base_array_ptr, 0); lbValue len = lb_const_int(p->module, t_int, slice_len); lb_fill_slice(p, slice, base_elem, len); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 9f28e45e0..e70cc503e 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1579,7 +1579,8 @@ gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValu lb_addr_store(p, x, value); } else { if (!is_default_case) { - GB_ASSERT_MSG(are_types_identical(e->type, type_deref(value.type)), "%s %s", type_to_string(e->type), type_to_string(value.type)); + Type *clause_type = e->type; + GB_ASSERT_MSG(are_types_identical(type_deref(clause_type), type_deref(value.type)), "%s %s", type_to_string(clause_type), type_to_string(value.type)); } lb_add_entity(p->module, e, value); } @@ -1735,10 +1736,17 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss for (Ast *clause : body->stmts) { ast_node(cc, CaseClause, clause); + + Entity *case_entity = implicit_entity_of_node(clause); lb_open_scope(p, cc->scope); + if (cc->list.count == 0) { lb_start_block(p, default_block); - lb_store_type_case_implicit(p, clause, parent_value, true); + if (case_entity->flags & EntityFlag_Value) { + lb_store_type_case_implicit(p, clause, parent_value, true); + } else { + lb_store_type_case_implicit(p, clause, parent_ptr, true); + } lb_type_case_body(p, ss->label, clause, p->curr_block, done); continue; } @@ -1768,7 +1776,6 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss LLVMAddCase(switch_instr, on_val.value, body->block); } - Entity *case_entity = implicit_entity_of_node(clause); lb_start_block(p, body); @@ -1781,6 +1788,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss } else if (switch_kind == TypeSwitch_Any) { data = lb_emit_load(p, lb_emit_struct_ep(p, parent_ptr, 0)); } + GB_ASSERT(is_type_pointer(data.type)); Type *ct = case_entity->type; Type *ct_ptr = alloc_type_pointer(ct); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 2c4abbb4d..638170bfc 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -421,7 +421,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } TokenPos pos = t->Named.type_name->token.pos; - lbValue loc = lb_const_source_code_location_const(m, proc_name, pos); + lbValue loc = lb_const_source_code_location_as_global_ptr(m, proc_name, pos); LLVMValueRef vals[4] = { lb_const_string(m, t->Named.type_name->token.string).value, @@ -810,19 +810,18 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ case Type_Struct: { tag_type = t_type_info_struct; - LLVMValueRef vals[13] = {}; + LLVMValueRef vals[11] = {}; { - lbValue is_packed = lb_const_bool(m, t_bool, t->Struct.is_packed); - lbValue is_raw_union = lb_const_bool(m, t_bool, t->Struct.is_raw_union); - lbValue is_no_copy = lb_const_bool(m, t_bool, t->Struct.is_no_copy); - lbValue is_custom_align = lb_const_bool(m, t_bool, t->Struct.custom_align != 0); - vals[5] = is_packed.value; - vals[6] = is_raw_union.value; - vals[7] = is_no_copy.value; - vals[8] = is_custom_align.value; + u8 flags = 0; + if (t->Struct.is_packed) flags |= 1<<0; + if (t->Struct.is_raw_union) flags |= 1<<1; + if (t->Struct.is_no_copy) flags |= 1<<2; + if (t->Struct.custom_align) flags |= 1<<3; + + vals[6] = lb_const_int(m, t_u8, flags).value; if (is_type_comparable(t) && !is_type_simple_compare(t)) { - vals[9] = lb_equal_proc_for_type(m, t).value; + vals[10] = lb_equal_proc_for_type(m, t).value; } @@ -831,11 +830,11 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ lbValue soa_kind = lb_const_value(m, kind_type, exact_value_i64(t->Struct.soa_kind)); LLVMValueRef soa_type = get_type_info_ptr(m, t->Struct.soa_elem); - lbValue soa_len = lb_const_int(m, t_int, t->Struct.soa_count); + lbValue soa_len = lb_const_int(m, t_i32, t->Struct.soa_count); - vals[10] = soa_kind.value; - vals[11] = soa_type; - vals[12] = soa_len.value; + vals[7] = soa_kind.value; + vals[8] = soa_len.value; + vals[9] = soa_type; } } @@ -882,12 +881,13 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } - lbValue cv = lb_const_int(m, t_int, count); - vals[0] = llvm_const_slice(m, memory_types, cv); - vals[1] = llvm_const_slice(m, memory_names, cv); - vals[2] = llvm_const_slice(m, memory_offsets, cv); - vals[3] = llvm_const_slice(m, memory_usings, cv); - vals[4] = llvm_const_slice(m, memory_tags, cv); + lbValue cv = lb_const_int(m, t_i32, count); + vals[0] = memory_types.value; + vals[1] = memory_names.value; + vals[2] = memory_offsets.value; + vals[3] = memory_usings.value; + vals[4] = memory_tags.value; + vals[5] = cv.value; } for (isize i = 0; i < gb_count_of(vals); i++) { if (vals[i] == nullptr) { @@ -994,7 +994,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ { tag_type = t_type_info_bit_field; - LLVMValueRef vals[6] = {}; + LLVMValueRef vals[7] = {}; vals[0] = get_type_info_ptr(m, t->BitField.backing_type); isize count = t->BitField.fields.count; if (count > 0) { @@ -1035,11 +1035,12 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } lbValue cv = lb_const_int(m, t_int, count); - vals[1] = llvm_const_slice(m, memory_names, cv); - vals[2] = llvm_const_slice(m, memory_types, cv); - vals[3] = llvm_const_slice(m, memory_bit_sizes, cv); - vals[4] = llvm_const_slice(m, memory_bit_offsets, cv); - vals[5] = llvm_const_slice(m, memory_tags, cv); + vals[1] = memory_names.value; + vals[2] = memory_types.value; + vals[3] = memory_bit_sizes.value; + vals[4] = memory_bit_offsets.value; + vals[5] = memory_tags.value; + vals[6] = cv.value; } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 98ed0c57e..1165476be 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2029,7 +2029,11 @@ gb_internal void lb_set_wasm_procedure_import_attributes(LLVMValueRef value, Ent GB_ASSERT(foreign_library->LibraryName.paths.count == 1); module_name = foreign_library->LibraryName.paths[0]; - + + if (string_ends_with(module_name, str_lit(".o"))) { + return; + } + if (string_starts_with(import_name, module_name)) { import_name = substring(import_name, module_name.len+WASM_MODULE_NAME_SEPARATOR.len, import_name.len); } diff --git a/src/main.cpp b/src/main.cpp index dca2b7fc8..41a95338b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,6 +61,7 @@ gb_global Timings global_timings = {0}; #include "llvm-c/Types.h" #else #include +#include #endif #include "parser.hpp" @@ -70,6 +71,8 @@ gb_global Timings global_timings = {0}; #include "checker.cpp" #include "docs.cpp" +#include "cached.cpp" + #include "linker.cpp" #if defined(GB_SYSTEM_WINDOWS) && defined(ODIN_TILDE_BACKEND) @@ -94,16 +97,38 @@ gb_global Timings global_timings = {0}; #include "bug_report.cpp" // NOTE(bill): 'name' is used in debugging and profiling modes -gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { +gb_internal i32 system_exec_command_line_app_internal(bool exit_on_err, char const *name, char const *fmt, va_list va) { isize const cmd_cap = 64<<20; // 64 MiB should be more than enough char *cmd_line = gb_alloc_array(gb_heap_allocator(), char, cmd_cap); isize cmd_len = 0; - va_list va; i32 exit_code = 0; - va_start(va, fmt); cmd_len = gb_snprintf_va(cmd_line, cmd_cap-1, fmt, va); - va_end(va); + + if (build_context.print_linker_flags) { + // NOTE(bill): remove the first argument (the executable) from the executable list + // and then print it for the "linker flags" + while (*cmd_line && gb_char_is_space(*cmd_line)) { + cmd_line++; + } + if (*cmd_line == '\"') for (cmd_line++; *cmd_line; cmd_line++) { + if (*cmd_line == '\\') { + cmd_line++; + if (*cmd_line == '\"') { + cmd_line++; + } + } else if (*cmd_line == '\"') { + cmd_line++; + break; + } + } + while (*cmd_line && gb_char_is_space(*cmd_line)) { + cmd_line++; + } + + fprintf(stdout, "%s\n", cmd_line); + return exit_code; + } #if defined(GB_SYSTEM_WINDOWS) STARTUPINFOW start_info = {gb_size_of(STARTUPINFOW)}; @@ -143,18 +168,36 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, gb_printf_err("%s\n\n", cmd_line); } exit_code = system(cmd_line); + if (exit_on_err && WIFSIGNALED(exit_code)) { + raise(WTERMSIG(exit_code)); + } if (WIFEXITED(exit_code)) { exit_code = WEXITSTATUS(exit_code); } #endif - if (exit_code) { + if (exit_on_err && exit_code) { exit(exit_code); } return exit_code; } +gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + i32 exit_code = system_exec_command_line_app_internal(/* exit_on_err= */ false, name, fmt, va); + va_end(va); + return exit_code; +} + +gb_internal void system_must_exec_command_line_app(char const *name, char const *fmt, ...) { + va_list va; + va_start(va, fmt); + system_exec_command_line_app_internal(/* exit_on_err= */ true, name, fmt, va); + va_end(va); +} + #if defined(GB_SYSTEM_WINDOWS) #define popen _popen #define pclose _pclose @@ -343,10 +386,15 @@ enum BuildFlagKind { BuildFlag_MinLinkLibs, + BuildFlag_PrintLinkerFlags, + // internal use only BuildFlag_InternalIgnoreLazy, BuildFlag_InternalIgnoreLLVMBuild, BuildFlag_InternalIgnorePanic, + BuildFlag_InternalModulePerFile, + BuildFlag_InternalCached, + BuildFlag_InternalNoInline, BuildFlag_Tilde, @@ -544,9 +592,14 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_MinLinkLibs, str_lit("min-link-libs"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_PrintLinkerFlags, str_lit("print-linker-flags"), BuildFlagParam_None, Command_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); - add_flag(&build_flags, BuildFlag_InternalIgnorePanic, str_lit("internal-ignore-panic"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalIgnorePanic, str_lit("internal-ignore-panic"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalModulePerFile, str_lit("internal-module-per-file"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalCached, str_lit("internal-cached"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalNoInline, str_lit("internal-no-inline"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -554,6 +607,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Sanitize, str_lit("sanitize"), BuildFlagParam_String, Command__does_build, true); + #if defined(GB_SYSTEM_WINDOWS) add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_ResourceFile, str_lit("resource"), BuildFlagParam_String, Command__does_build); @@ -812,8 +866,7 @@ gb_internal bool parse_build_flags(Array args) { gb_printf_err("\tjson\n"); bad_flags = true; } - - break; + break; } case BuildFlag_ExportDependenciesFile: { GB_ASSERT(value.kind == ExactValue_String); @@ -1351,6 +1404,10 @@ gb_internal bool parse_build_flags(Array args) { build_context.min_link_libs = true; break; + case BuildFlag_PrintLinkerFlags: + build_context.print_linker_flags = true; + break; + case BuildFlag_InternalIgnoreLazy: build_context.ignore_lazy = true; break; @@ -1360,6 +1417,18 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_InternalIgnorePanic: build_context.ignore_panic = true; break; + case BuildFlag_InternalModulePerFile: + build_context.module_per_file = true; + build_context.use_separate_modules = true; + break; + case BuildFlag_InternalCached: + build_context.cached = true; + build_context.use_separate_modules = true; + break; + case BuildFlag_InternalNoInline: + build_context.internal_no_inline = true; + break; + case BuildFlag_Tilde: build_context.tilde_backend = true; break; @@ -1379,6 +1448,7 @@ gb_internal bool parse_build_flags(Array args) { } break; + #if defined(GB_SYSTEM_WINDOWS) case BuildFlag_IgnoreVsSearch: { GB_ASSERT(value.kind == ExactValue_Invalid); @@ -1390,8 +1460,9 @@ gb_internal bool parse_build_flags(Array args) { String path = value.value_string; path = string_trim_whitespace(path); if (is_build_flag_path_valid(path)) { - if(!string_ends_with(path, str_lit(".rc"))) { - gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path)); + bool is_resource = string_ends_with(path, str_lit(".rc")) || string_ends_with(path, str_lit(".res")); + if(!is_resource) { + gb_printf_err("Invalid -resource path %.*s, missing .rc or .res file\n", LIT(path)); bad_flags = true; break; } else if (!gb_file_exists((const char *)path.text)) { @@ -1519,6 +1590,16 @@ gb_internal bool parse_build_flags(Array args) { gb_printf_err("`-export-timings:` requires `-show-timings` or `-show-more-timings` to be present\n"); bad_flags = true; } + + + if (build_context.export_dependencies_format != DependenciesExportUnspecified && build_context.print_linker_flags) { + gb_printf_err("-export-dependencies cannot be used with -print-linker-flags\n"); + bad_flags = true; + } else if (build_context.show_timings && build_context.print_linker_flags) { + gb_printf_err("-show-timings/-show-more-timings cannot be used with -print-linker-flags\n"); + bad_flags = true; + } + return !bad_flags; } @@ -1854,7 +1935,13 @@ gb_internal void show_timings(Checker *c, Timings *t) { } } -gb_internal void export_dependencies(Parser *p) { +gb_internal GB_COMPARE_PROC(file_path_cmp) { + AstFile *x = *(AstFile **)a; + AstFile *y = *(AstFile **)b; + return string_compare(x->fullpath, y->fullpath); +} + +gb_internal void export_dependencies(Checker *c) { GB_ASSERT(build_context.export_dependencies_format != DependenciesExportUnspecified); if (build_context.export_dependencies_file.len <= 0) { @@ -1863,6 +1950,8 @@ gb_internal void export_dependencies(Parser *p) { return; } + Parser *p = c->parser; + gbFile f = {}; char * fileName = (char *)build_context.export_dependencies_file.text; gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); @@ -1873,6 +1962,26 @@ gb_internal void export_dependencies(Parser *p) { } defer (gb_file_close(&f)); + + auto files = array_make(heap_allocator()); + for (AstPackage *pkg : p->packages) { + for (AstFile *f : pkg->files) { + array_add(&files, f); + } + } + array_sort(files, file_path_cmp); + + + auto load_files = array_make(heap_allocator()); + for (auto const &entry : c->info.load_file_cache) { + auto *cache = entry.value; + if (!cache || !cache->exists) { + continue; + } + array_add(&load_files, cache); + } + array_sort(files, file_cache_sort_cmp); + if (build_context.export_dependencies_format == DependenciesExportMake) { String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); @@ -1881,26 +1990,25 @@ gb_internal void export_dependencies(Parser *p) { isize current_line_length = exe_name.len + 1; - for(AstPackage *pkg : p->packages) { - for(AstFile *file : pkg->files) { - /* Arbitrary line break value. Maybe make this better? */ - if (current_line_length >= 80-2) { - gb_file_write(&f, " \\\n ", 4); - current_line_length = 1; - } + for_array(i, files) { + AstFile *file = files[i]; + /* Arbitrary line break value. Maybe make this better? */ + if (current_line_length >= 80-2) { + gb_file_write(&f, " \\\n ", 4); + current_line_length = 1; + } - gb_file_write(&f, " ", 1); - current_line_length++; + gb_file_write(&f, " ", 1); + current_line_length++; - for (isize k = 0; k < file->fullpath.len; k++) { - char part = file->fullpath.text[k]; - if (part == ' ') { - gb_file_write(&f, "\\", 1); - current_line_length++; - } - gb_file_write(&f, &part, 1); + for (isize k = 0; k < file->fullpath.len; k++) { + char part = file->fullpath.text[k]; + if (part == ' ') { + gb_file_write(&f, "\\", 1); current_line_length++; } + gb_file_write(&f, &part, 1); + current_line_length++; } } @@ -1910,14 +2018,30 @@ gb_internal void export_dependencies(Parser *p) { gb_fprintf(&f, "\t\"source_files\": [\n"); - for(AstPackage *pkg : p->packages) { - for(AstFile *file : pkg->files) { - gb_fprintf(&f, "\t\t\"%.*s\",\n", LIT(file->fullpath)); + for_array(i, files) { + AstFile *file = files[i]; + gb_fprintf(&f, "\t\t\"%.*s\"", LIT(file->fullpath)); + if (i+1 == files.count) { + gb_fprintf(&f, ","); } + gb_fprintf(&f, "\n"); } gb_fprintf(&f, "\t],\n"); + gb_fprintf(&f, "\t\"load_files\": [\n"); + + for_array(i, load_files) { + LoadFileCache *cache = load_files[i]; + gb_fprintf(&f, "\t\t\"%.*s\"", LIT(cache->path)); + if (i+1 == load_files.count) { + gb_fprintf(&f, ","); + } + gb_fprintf(&f, "\n"); + } + + gb_fprintf(&f, "\t]\n"); + gb_fprintf(&f, "}\n"); } } @@ -2148,7 +2272,6 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); print_usage_line(3, "-build-mode:static Builds as a statically linked library."); - print_usage_line(3, "-build-mode:lib Builds as an static library."); print_usage_line(3, "-build-mode:obj Builds as an object file."); print_usage_line(3, "-build-mode:object Builds as an object file."); print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); @@ -2195,9 +2318,9 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(0, ""); print_usage_line(1, "-use-separate-modules"); - print_usage_line(1, "[EXPERIMENTAL]"); print_usage_line(2, "The backend generates multiple build units which are then linked together."); print_usage_line(2, "Normally, a single build unit is generated for a standard project."); + print_usage_line(2, "This is the default behaviour on Windows for '-o:none' and '-o:minimal' builds."); print_usage_line(0, ""); } @@ -2346,6 +2469,12 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(0, ""); } + if (build) { + print_usage_line(1, "-print-linker-flags"); + print_usage_line(2, "Prints the all of the flags/arguments that will be passed to the linker."); + print_usage_line(0, ""); + } + if (check) { print_usage_line(1, "-disallow-do"); print_usage_line(2, "Disallows the 'do' keyword in the project."); @@ -2432,6 +2561,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "[Windows only]"); print_usage_line(2, "Defines the resource file for the executable."); print_usage_line(2, "Example: -resource:path/to/file.rc"); + print_usage_line(2, "or: -resource:path/to/file.res for a precompiled one."); print_usage_line(0, ""); print_usage_line(1, "-pdb-name:"); @@ -2937,6 +3067,8 @@ int main(int arg_count, char const **arg_ptr) { } else if (command == "root") { gb_printf("%.*s", LIT(odin_root_dir())); return 0; + } else if (command == "clear-cache") { + return try_clear_cache() ? 0 : 1; } else { String argv1 = {}; if (args.count > 1) { @@ -3149,12 +3281,19 @@ int main(int arg_count, char const **arg_ptr) { print_all_errors(); } - MAIN_TIME_SECTION("type check"); checker->parser = parser; init_checker(checker); - defer (destroy_checker(checker)); + defer (destroy_checker(checker)); // this is here because of a `goto` + if (build_context.cached && parser->total_seen_load_directive_count.load() == 0) { + MAIN_TIME_SECTION("check cached build (pre-semantic check)"); + if (try_cached_build(checker, args)) { + goto end_of_code_gen; + } + } + + MAIN_TIME_SECTION("type check"); check_parsed_files(checker); check_defines(&build_context, checker); if (any_errors()) { @@ -3207,6 +3346,13 @@ int main(int arg_count, char const **arg_ptr) { return 0; } + if (build_context.cached) { + MAIN_TIME_SECTION("check cached build"); + if (try_cached_build(checker, args)) { + goto end_of_code_gen; + } + } + #if ALLOW_TILDE if (build_context.tilde_backend) { LinkerData linker_data = {}; @@ -3226,7 +3372,7 @@ int main(int arg_count, char const **arg_ptr) { } if (build_context.export_dependencies_format != DependenciesExportUnspecified) { - export_dependencies(parser); + export_dependencies(checker); } return result; } @@ -3235,11 +3381,16 @@ int main(int arg_count, char const **arg_ptr) { } else #endif { - MAIN_TIME_SECTION("LLVM API Code Gen"); lbGenerator *gen = gb_alloc_item(permanent_allocator(), lbGenerator); if (!lb_init_generator(gen, checker)) { return 1; } + + gbString label_code_gen = gb_string_make(heap_allocator(), "LLVM API Code Gen"); + if (gen->modules.count > 1) { + label_code_gen = gb_string_append_fmt(label_code_gen, " ( %4td modules )", gen->modules.count); + } + MAIN_TIME_SECTION_WITH_LEN(label_code_gen, gb_string_length(label_code_gen)); if (lb_generate_code(gen)) { switch (build_context.build_mode) { case BuildMode_Executable: @@ -3252,7 +3403,7 @@ int main(int arg_count, char const **arg_ptr) { } if (build_context.export_dependencies_format != DependenciesExportUnspecified) { - export_dependencies(parser); + export_dependencies(checker); } return result; } @@ -3263,19 +3414,27 @@ int main(int arg_count, char const **arg_ptr) { remove_temp_files(gen); } +end_of_code_gen:; + if (build_context.show_timings) { show_timings(checker, &global_timings); } if (build_context.export_dependencies_format != DependenciesExportUnspecified) { - export_dependencies(parser); + export_dependencies(checker); + } + + + if (!build_context.build_cache_data.copy_already_done && + build_context.cached) { + try_copy_executable_to_cache(); } if (run_output) { String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); - return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); + system_must_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); } return 0; } diff --git a/src/parser.cpp b/src/parser.cpp index cc12432e0..ba67d4594 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -112,17 +112,17 @@ gb_internal isize ast_node_size(AstKind kind) { } -gb_global std::atomic global_total_node_memory_allocated; +// gb_global std::atomic global_total_node_memory_allocated; // NOTE(bill): And this below is why is I/we need a new language! Discriminated unions are a pain in C/C++ gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { isize size = ast_node_size(kind); - Ast *node = cast(Ast *)arena_alloc(&global_thread_local_ast_arena, size, 16); + Ast *node = cast(Ast *)arena_alloc(get_arena(ThreadArena_Permanent), size, 16); node->kind = kind; node->file_id = f ? f->id : 0; - global_total_node_memory_allocated.fetch_add(size); + // global_total_node_memory_allocated.fetch_add(size); return node; } @@ -787,6 +787,9 @@ gb_internal Ast *ast_basic_directive(AstFile *f, Token token, Token name) { Ast *result = alloc_ast_node(f, Ast_BasicDirective); result->BasicDirective.token = token; result->BasicDirective.name = name; + if (string_starts_with(name.string, str_lit("load"))) { + f->seen_load_directive_count++; + } return result; } @@ -3135,7 +3138,7 @@ gb_internal Ast *parse_call_expr(AstFile *f, Ast *operand) { Ast *call = ast_call_expr(f, operand, args, open_paren, close_paren, ellipsis); Ast *o = unparen_expr(operand); - if (o->kind == Ast_SelectorExpr && o->SelectorExpr.token.kind == Token_ArrowRight) { + if (o && o->kind == Ast_SelectorExpr && o->SelectorExpr.token.kind == Token_ArrowRight) { return ast_selector_call_expr(f, o->SelectorExpr.token, o, call); } @@ -4011,6 +4014,7 @@ struct ParseFieldPrefixMapping { gb_global ParseFieldPrefixMapping const parse_field_prefix_mappings[] = { {str_lit("using"), Token_using, FieldFlag_using}, {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, + {str_lit("no_capture"), Token_Hash, FieldFlag_no_capture}, {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, {str_lit("const"), Token_Hash, FieldFlag_const}, {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, @@ -5254,6 +5258,38 @@ gb_internal Ast *parse_stmt(AstFile *f) { } else if (tag == "include") { syntax_error(token, "#include is not a valid import declaration kind. Did you mean 'import'?"); s = ast_bad_stmt(f, token, f->curr_token); + } else if (tag == "define") { + s = ast_bad_stmt(f, token, f->curr_token); + + if (name.pos.line == f->curr_token.pos.line) { + bool call_like = false; + Ast *macro_expr = nullptr; + Token ident = f->curr_token; + if (allow_token(f, Token_Ident) && + name.pos.line == f->curr_token.pos.line) { + if (f->curr_token.kind == Token_OpenParen && f->curr_token.pos.column == ident.pos.column+ident.string.len) { + call_like = true; + (void)parse_call_expr(f, nullptr); + } + + if (name.pos.line == f->curr_token.pos.line && f->curr_token.kind != Token_Semicolon) { + macro_expr = parse_expr(f, false); + } + } + + ERROR_BLOCK(); + syntax_error(ident, "#define is not a valid declaration, Odin does not have a C-like preprocessor."); + if (macro_expr == nullptr || call_like) { + error_line("\tNote: Odin does not support macros\n"); + } else { + gbString s = expr_to_string(macro_expr); + error_line("\tSuggestion: Did you mean '%.*s :: %s'?\n", LIT(ident.string), s); + gb_string_free(s); + } + } else { + syntax_error(token, "#define is not a valid declaration, Odin does not have a C-like preprocessor."); + } + } else { syntax_error(token, "Unknown tag directive used: '%.*s'", LIT(tag)); s = ast_bad_stmt(f, token, f->curr_token); @@ -5377,7 +5413,7 @@ gb_internal ParseFileError init_ast_file(AstFile *f, String const &fullpath, Tok if (!string_ends_with(f->fullpath, str_lit(".odin"))) { return ParseFile_WrongExtension; } - zero_item(&f->tokenizer); + gb_zero_item(&f->tokenizer); f->tokenizer.curr_file_id = f->id; TokenizerInitError err = init_tokenizer_from_fullpath(&f->tokenizer, f->fullpath, build_context.copy_file_contents); @@ -5609,7 +5645,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const pkg->foreign_files.allocator = permanent_allocator(); // NOTE(bill): Single file initial package - if (kind == Package_Init && string_ends_with(path, FILE_EXT)) { + if (kind == Package_Init && !path_is_directory(path) && string_ends_with(path, FILE_EXT)) { FileInfo fi = {}; fi.name = filename_from_path(path); fi.fullpath = path; @@ -5861,7 +5897,6 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node return false; } - if (collection_name.len > 0) { // NOTE(bill): `base:runtime` == `core:runtime` if (collection_name == "core") { @@ -6016,7 +6051,7 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas Token fp_token = fp->BasicLit.token; String file_str = string_trim_whitespace(string_value_from_token(f, fp_token)); String fullpath = file_str; - if (allow_check_foreign_filepath()) { + if (!is_arch_wasm() || string_ends_with(fullpath, str_lit(".o"))) { String foreign_path = {}; bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); if (!ok) { @@ -6380,8 +6415,6 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } else if (lc == "+lazy") { if (build_context.ignore_lazy) { // Ignore - } else if (f->flags & AstFile_IsTest) { - // Ignore } else if (f->pkg->kind == Package_Init && build_context.command_kind == Command_doc) { // Ignore } else { @@ -6499,11 +6532,6 @@ gb_internal ParseFileError process_imported_file(Parser *p, ImportedFile importe if (build_context.command_kind == Command_test) { String name = file->fullpath; name = remove_extension_from_path(name); - - String test_suffix = str_lit("_test"); - if (string_ends_with(name, test_suffix) && name != test_suffix) { - file->flags |= AstFile_IsTest; - } } @@ -6538,6 +6566,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { GB_ASSERT(init_filename.text[init_filename.len] == 0); String init_fullpath = path_to_full_path(permanent_allocator(), init_filename); + if (!path_is_directory(init_fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(init_fullpath, ext)) { @@ -6551,9 +6580,8 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { - String short_path = filename_from_path(path); - char *cpath = alloc_cstring(temporary_allocator(), short_path); - if (gb_file_exists(cpath)) { + String output_path = path_to_string(temporary_allocator(), build_context.build_paths[8]); + if (path_is_directory(output_path)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } @@ -6621,6 +6649,13 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } } } + + for (AstPackage *pkg : p->packages) { + for (AstFile *file : pkg->files) { + p->total_seen_load_directive_count += file->seen_load_directive_count; + } + } + return ParseFile_None; } diff --git a/src/parser.hpp b/src/parser.hpp index 02f2af28d..565a8e621 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -74,7 +74,6 @@ enum AstFileFlag : u32 { AstFile_IsPrivatePkg = 1<<0, AstFile_IsPrivateFile = 1<<1, - AstFile_IsTest = 1<<3, AstFile_IsLazy = 1<<4, AstFile_NoInstrumentation = 1<<5, @@ -141,6 +140,8 @@ struct AstFile { // This is effectively a queue but does not require any multi-threading capabilities Array delayed_decls_queues[AstDelayQueue_COUNT]; + std::atomic seen_load_directive_count; + #define PARSER_MAX_FIX_COUNT 6 isize fix_count; TokenPos fix_prev_pos; @@ -211,6 +212,8 @@ struct Parser { std::atomic total_token_count; std::atomic total_line_count; + std::atomic total_seen_load_directive_count; + // TODO(bill): What should this mutex be per? // * Parser // * Package @@ -328,8 +331,10 @@ enum FieldFlag : u32 { FieldFlag_by_ptr = 1<<8, FieldFlag_no_broadcast = 1<<9, // disallow array programming + FieldFlag_no_capture = 1<<11, + // Internal use by the parser only - FieldFlag_Tags = 1<<10, + FieldFlag_Tags = 1<<15, FieldFlag_Results = 1<<16, @@ -337,7 +342,10 @@ enum FieldFlag : u32 { FieldFlag_Invalid = 1u<<31, // Parameter List Restrictions - FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast, + FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg| + FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast| + FieldFlag_no_capture, + FieldFlag_Struct = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags, }; @@ -870,10 +878,8 @@ gb_internal gb_inline bool is_ast_when_stmt(Ast *node) { return node->kind == Ast_WhenStmt; } -gb_global gb_thread_local Arena global_thread_local_ast_arena = {}; - gb_internal gb_inline gbAllocator ast_allocator(AstFile *f) { - return arena_allocator(&global_thread_local_ast_arena); + return permanent_allocator(); } gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind); diff --git a/src/path.cpp b/src/path.cpp index 26ccb7cbf..2c08ddd98 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -152,6 +152,13 @@ gb_internal String path_to_string(gbAllocator a, Path path) { return res; } +gb_internal String quote_path(gbAllocator a, Path path) { + String temp = path_to_string(a, path); + String quoted = concatenate3_strings(a, str_lit("\""), temp, str_lit("\"")); + gb_free(a, temp.text); + return quoted; +} + // NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`. gb_internal String path_to_full_path(gbAllocator a, Path path) { String temp = path_to_string(heap_allocator(), path); diff --git a/src/queue.cpp b/src/queue.cpp index 2ad9cb29f..dee9ad1f8 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -16,7 +16,7 @@ struct MPSCQueue { std::atomic count; }; -template gb_internal void mpsc_init (MPSCQueue *q); +template gb_internal void mpsc_init (MPSCQueue *q, gbAllocator const &allocator); template gb_internal void mpsc_destroy(MPSCQueue *q); template gb_internal isize mpsc_enqueue(MPSCQueue *q, T const &value); template gb_internal bool mpsc_dequeue(MPSCQueue *q, T *value_); diff --git a/src/string.cpp b/src/string.cpp index 86eddeddd..3c7d96934 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -88,6 +88,13 @@ gb_internal char *alloc_cstring(gbAllocator a, String s) { return c_str; } +gb_internal wchar_t *alloc_wstring(gbAllocator a, String16 s) { + wchar_t *c_str = gb_alloc_array(a, wchar_t, s.len+1); + gb_memmove(c_str, s.text, s.len*2); + c_str[s.len] = '\0'; + return c_str; +} + gb_internal gb_inline bool str_eq_ignore_case(String const &a, String const &b) { if (a.len == b.len) { @@ -329,22 +336,22 @@ gb_internal bool string_contains_char(String const &s, u8 c) { } gb_internal bool string_contains_string(String const &haystack, String const &needle) { - if (needle.len == 0) return true; - if (needle.len > haystack.len) return false; + if (needle.len == 0) return true; + if (needle.len > haystack.len) return false; - for (isize i = 0; i <= haystack.len - needle.len; i++) { - bool found = true; - for (isize j = 0; j < needle.len; j++) { - if (haystack[i + j] != needle[j]) { - found = false; - break; - } - } - if (found) { - return true; - } - } - return false; + for (isize i = 0; i <= haystack.len - needle.len; i++) { + bool found = true; + for (isize j = 0; j < needle.len; j++) { + if (haystack[i + j] != needle[j]) { + found = false; + break; + } + } + if (found) { + return true; + } + } + return false; } gb_internal String filename_from_path(String s) { @@ -543,6 +550,40 @@ gb_internal String string16_to_string(gbAllocator a, String16 s) { +gb_internal String temporary_directory(gbAllocator allocator) { +#if defined(GB_SYSTEM_WINDOWS) + DWORD n = GetTempPathW(0, nullptr); + if (n == 0) { + return String{0}; + } + DWORD len = gb_max(MAX_PATH, n); + wchar_t *b = gb_alloc_array(heap_allocator(), wchar_t, len+1); + defer (gb_free(heap_allocator(), b)); + n = GetTempPathW(len, b); + if (n == 3 && b[1] == ':' && b[2] == '\\') { + + } else if (n > 0 && b[n-1] == '\\') { + n -= 1; + } + b[n] = 0; + String16 s = make_string16(b, n); + return string16_to_string(allocator, s); +#else + char const *tmp_env = gb_get_env("TMPDIR", allocator); + if (tmp_env) { + return make_string_c(tmp_env); + } + +#if defined(P_tmpdir) + String tmp_macro = make_string_c(P_tmpdir); + if (tmp_macro.len != 0) { + return copy_string(allocator, tmp_macro); + } +#endif + + return copy_string(allocator, str_lit("/tmp")); +#endif +} diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 5dbbe37c4..8363a4553 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -3,20 +3,28 @@ struct WorkerTask; struct ThreadPool; -gb_thread_local Thread *current_thread; +gb_global gb_thread_local Thread *current_thread; +gb_internal Thread *get_current_thread(void) { + return current_thread; +} gb_internal void thread_pool_init(ThreadPool *pool, isize worker_count, char const *worker_name); gb_internal void thread_pool_destroy(ThreadPool *pool); gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data); gb_internal void thread_pool_wait(ThreadPool *pool); +enum GrabState { + Grab_Success = 0, + Grab_Empty = 1, + Grab_Failed = 2, +}; + struct ThreadPool { - gbAllocator threads_allocator; - Slice threads; + gbAllocator threads_allocator; + Slice threads; std::atomic running; Futex tasks_available; - Futex tasks_left; }; @@ -46,7 +54,7 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { for_array_off(i, 1, pool->threads) { Thread *t = &pool->threads[i]; - pool->tasks_available.fetch_add(1, std::memory_order_relaxed); + pool->tasks_available.fetch_add(1, std::memory_order_acquire); futex_broadcast(&pool->tasks_available); thread_join_and_destroy(t); } @@ -54,51 +62,86 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } +TaskRingBuffer *task_ring_grow(TaskRingBuffer *ring, isize bottom, isize top) { + TaskRingBuffer *new_ring = task_ring_init(ring->size * 2); + for (isize i = top; i < bottom; i++) { + new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; + } + return new_ring; +} + void thread_pool_queue_push(Thread *thread, WorkerTask task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(); + isize bot = thread->queue.bottom.load(std::memory_order_relaxed); + isize top = thread->queue.top.load(std::memory_order_acquire); + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; + isize size = bot - top; + if (size > (cur_ring->size - 1)) { + // Queue is full + thread->queue.ring = task_ring_grow(thread->queue.ring, bot, top); + cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + } - u64 new_head = (head + 1) & mask; - GB_ASSERT_MSG(new_head != tail, "Thread Queue Full!"); - - // This *must* be done in here, to avoid a potential race condition where we no longer own the slot by the time we're assigning - thread->queue[head] = task; - new_capture = (new_head << 32) | tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture)); + cur_ring->buffer[bot % cur_ring->size] = task; + std::atomic_thread_fence(std::memory_order_release); + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); thread->pool->tasks_left.fetch_add(1, std::memory_order_release); thread->pool->tasks_available.fetch_add(1, std::memory_order_relaxed); futex_broadcast(&thread->pool->tasks_available); } -bool thread_pool_queue_pop(Thread *thread, WorkerTask *task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(std::memory_order_acquire); +GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { + isize bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + thread->queue.bottom.store(bot, std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_seq_cst); - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; + isize top = thread->queue.top.load(std::memory_order_relaxed); + if (top <= bot) { - u64 new_tail = (tail + 1) & mask; - if (tail == head) { - return false; + // Queue is not empty + *task = cur_ring->buffer[bot % cur_ring->size]; + if (top == bot) { + // Only one entry left in queue + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return Grab_Empty; + } + + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return Grab_Success; } - // Making a copy of the task before we increment the tail, avoiding the same potential race condition as above - *task = thread->queue[tail]; + // We got a task without hitting a race + return Grab_Success; + } else { + // Queue is empty + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return Grab_Empty; + } +} - new_capture = (head << 32) | new_tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture, std::memory_order_release)); +GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { + isize top = thread->queue.top.load(std::memory_order_acquire); + std::atomic_thread_fence(std::memory_order_seq_cst); + isize bot = thread->queue.bottom.load(std::memory_order_acquire); - return true; + GrabState ret = Grab_Empty; + if (top < bot) { + // Queue is not empty + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_consume); + *task = cur_ring->buffer[top % cur_ring->size]; + + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + ret = Grab_Failed; + } else { + ret = Grab_Success; + } + } + return ret; } gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data) { @@ -115,12 +158,11 @@ gb_internal void thread_pool_wait(ThreadPool *pool) { while (pool->tasks_left.load(std::memory_order_acquire)) { // if we've got tasks on our queue, run them - while (thread_pool_queue_pop(current_thread, &task)) { + while (!thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); } - // is this mem-barriered enough? // This *must* be executed in this order, so the futex wakes immediately // if rem_tasks has changed since we checked last, otherwise the program @@ -145,7 +187,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { usize finished_tasks = 0; i32 state; - while (thread_pool_queue_pop(current_thread, &task)) { + while (!thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -167,7 +209,12 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { Thread *thread = &pool->threads.data[idx]; WorkerTask task; - if (thread_pool_queue_pop(thread, &task)) { + + GrabState ret = thread_pool_queue_steal(thread, &task); + switch (ret) { + case Grab_Empty: + continue; + case Grab_Success: task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -175,6 +222,8 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { futex_signal(&pool->tasks_left); } + /*fallthrough*/ + case Grab_Failed: goto main_loop_continue; } } @@ -182,6 +231,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { // if we've done all our work, and there's nothing to steal, go to sleep state = pool->tasks_available.load(std::memory_order_acquire); + if (!pool->running) { break; } futex_wait(&pool->tasks_available, state); main_loop_continue:; diff --git a/src/threading.cpp b/src/threading.cpp index 717dcb874..011b66028 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -46,6 +46,18 @@ typedef struct WorkerTask { void *data; } WorkerTask; +typedef struct TaskRingBuffer { + std::atomic size; + std::atomic buffer; +} TaskRingBuffer; + +typedef struct TaskQueue { + std::atomic top; + std::atomic bottom; + + std::atomic ring; +} TaskQueue; + struct Thread { #if defined(GB_SYSTEM_WINDOWS) void *win32_handle; @@ -54,13 +66,13 @@ struct Thread { #endif isize idx; + isize stack_size; - WorkerTask *queue; - size_t capacity; - std::atomic head_and_tail; - - isize stack_size; + struct TaskQueue queue; struct ThreadPool *pool; + + struct Arena *permanent_arena; + struct Arena *temporary_arena; }; typedef std::atomic Futex; @@ -551,6 +563,20 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif +gb_internal TaskRingBuffer *task_ring_init(isize size) { + TaskRingBuffer *ring = gb_alloc_item(heap_allocator(), TaskRingBuffer); + ring->size = size; + ring->buffer = gb_alloc_array(heap_allocator(), WorkerTask, ring->size); + return ring; +} + +gb_internal void thread_queue_destroy(TaskQueue *q) { + gb_free(heap_allocator(), (*q->ring).buffer); + gb_free(heap_allocator(), q->ring); +} + +gb_internal void thread_init_arenas(Thread *t); + gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { gb_zero_item(t); #if defined(GB_SYSTEM_WINDOWS) @@ -559,13 +585,13 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { t->posix_handle = 0; #endif - t->capacity = 1 << 14; // must be a power of 2 - t->queue = gb_alloc_array(heap_allocator(), WorkerTask, t->capacity); - t->head_and_tail = 0; + // Size must be a power of 2 + t->queue.ring = task_ring_init(1 << 14); t->pool = pool; t->idx = idx; -} + thread_init_arenas(t); +} gb_internal void thread_init_and_start(ThreadPool *pool, Thread *t, isize idx) { thread_init(pool, t, idx); @@ -598,7 +624,7 @@ gb_internal void thread_join_and_destroy(Thread *t) { t->posix_handle = 0; #endif - gb_free(heap_allocator(), t->queue); + thread_queue_destroy(&t->queue); } gb_internal void thread_set_name(Thread *t, char const *name) { @@ -770,13 +796,27 @@ gb_internal void futex_wait(Futex *f, Footex val) { #elif defined(GB_SYSTEM_OSX) +// IMPORTANT NOTE(laytan): We use `OS_SYNC_*_SHARED` and `UL_COMPARE_AND_WAIT_SHARED` flags here. +// these flags tell the kernel that we are using these futexes across different processes which +// causes it to opt-out of some optimisations. +// +// BUT this is not actually the case! We should be using the normal non-shared version and letting +// the kernel optimize (I've measured it to be about 10% faster at the parsing/type checking stages). +// +// However we have reports of people on MacOS running into kernel panics, and this seems to fix it for them. +// Which means there is probably a bug in the kernel in one of these non-shared optimisations causing the panic. +// +// The panic also doesn't seem to happen on normal M1 CPUs, and happen more on later CPUs or pro/max series. +// Probably because they have more going on in terms of threads etc. + #if __has_include() #define DARWIN_WAIT_ON_ADDRESS_AVAILABLE #include #endif -#define UL_COMPARE_AND_WAIT 0x00000001 -#define ULF_NO_ERRNO 0x01000000 +#define UL_COMPARE_AND_WAIT 0x00000001 +#define UL_COMPARE_AND_WAIT_SHARED 0x00000003 +#define ULF_NO_ERRNO 0x01000000 extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */ extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); @@ -785,7 +825,7 @@ gb_internal void futex_signal(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -800,7 +840,7 @@ gb_internal void futex_signal(Futex *f) { } else { #endif for (;;) { - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, 0); if (ret >= 0) { return; } @@ -821,7 +861,7 @@ gb_internal void futex_broadcast(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -837,7 +877,7 @@ gb_internal void futex_broadcast(Futex *f) { #endif for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); if (ret == 0) { return; } @@ -858,7 +898,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE); + int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_SHARED); if (ret >= 0) { if (*f != val) { return; @@ -876,7 +916,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { } else { #endif for (;;) { - int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0); + int ret = __ulock_wait(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { if (*f != val) { return; diff --git a/src/timings.cpp b/src/timings.cpp index 712e804cb..3f8402b36 100644 --- a/src/timings.cpp +++ b/src/timings.cpp @@ -146,9 +146,10 @@ gb_internal f64 time_stamp_as_us(TimeStamp const &ts, u64 freq) { return 1000000.0*time_stamp_as_s(ts, freq); } -#define MAIN_TIME_SECTION(str) do { debugf("[Section] %s\n", str); timings_start_section(&global_timings, str_lit(str)); } while (0) -#define TIME_SECTION(str) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, str_lit(str)); } while (0) -#define TIME_SECTION_WITH_LEN(str, len) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, make_string((u8 *)str, len)); } while (0) +#define MAIN_TIME_SECTION(str) do { debugf("[Section] %s\n", str); timings_start_section(&global_timings, str_lit(str)); } while (0) +#define MAIN_TIME_SECTION_WITH_LEN(str, len) do { debugf("[Section] %s\n", str); timings_start_section(&global_timings, make_string((u8 *)str, len)); } while (0) +#define TIME_SECTION(str) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, str_lit(str)); } while (0) +#define TIME_SECTION_WITH_LEN(str, len) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, make_string((u8 *)str, len)); } while (0) enum TimingUnit { diff --git a/src/types.cpp b/src/types.cpp index c3a5fb539..944760142 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -964,7 +964,7 @@ gb_internal Type *alloc_type(TypeKind kind) { // gbAllocator a = heap_allocator(); gbAllocator a = permanent_allocator(); Type *t = gb_alloc_item(a, Type); - zero_item(t); + gb_zero_item(t); t->kind = kind; t->cached_size = -1; t->cached_align = -1; @@ -1637,6 +1637,26 @@ gb_internal Type *base_array_type(Type *t) { return t; } + +gb_internal Type *base_any_array_type(Type *t) { + Type *bt = base_type(t); + if (is_type_array(bt)) { + return bt->Array.elem; + } else if (is_type_slice(bt)) { + return bt->Slice.elem; + } else if (is_type_dynamic_array(bt)) { + return bt->DynamicArray.elem; + } else if (is_type_enumerated_array(bt)) { + return bt->EnumeratedArray.elem; + } else if (is_type_simd_vector(bt)) { + return bt->SimdVector.elem; + } else if (is_type_matrix(bt)) { + return bt->Matrix.elem; + } + return t; +} + + gb_internal bool is_type_generic(Type *t) { t = base_type(t); return t->kind == Type_Generic; @@ -2011,6 +2031,24 @@ gb_internal bool is_type_valid_bit_set_elem(Type *t) { return false; } + +gb_internal bool is_valid_bit_field_backing_type(Type *type) { + if (type == nullptr) { + return false; + } + type = base_type(type); + if (is_type_untyped(type)) { + return false; + } + if (is_type_integer(type)) { + return true; + } + if (type->kind == Type_Array) { + return is_type_integer(type->Array.elem); + } + return false; +} + gb_internal Type *bit_set_to_int(Type *t) { GB_ASSERT(is_type_bit_set(t)); Type *bt = base_type(t); @@ -2018,6 +2056,9 @@ gb_internal Type *bit_set_to_int(Type *t) { if (underlying != nullptr && is_type_integer(underlying)) { return underlying; } + if (underlying != nullptr && is_valid_bit_field_backing_type(underlying)) { + return underlying; + } i64 sz = type_size_of(t); switch (sz) { @@ -2923,11 +2964,14 @@ gb_internal Type *c_vararg_promote_type(Type *type) { if (core->kind == Type_Basic) { switch (core->Basic.kind) { + case Basic_f16: case Basic_f32: case Basic_UntypedFloat: return t_f64; + case Basic_f16le: case Basic_f32le: return t_f64le; + case Basic_f16be: case Basic_f32be: return t_f64be; diff --git a/tests/benchmark/crypto/benchmark_crypto.odin b/tests/benchmark/crypto/benchmark_crypto.odin index e90216ad6..b2ac4bca3 100644 --- a/tests/benchmark/crypto/benchmark_crypto.odin +++ b/tests/benchmark/crypto/benchmark_crypto.odin @@ -28,6 +28,32 @@ benchmark_crypto :: proc(t: ^testing.T) { strings.builder_destroy(&str) } + { + name := "AES256-CTR 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_ctr, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-CTR 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-CTR 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } { name := "ChaCha20 64 bytes" options := &time.Benchmark_Options { @@ -323,6 +349,36 @@ _benchmark_chacha20poly1305 :: proc( return nil } +@(private) +_benchmark_aes256_ctr :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [aes.CTR_IV_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + ctx: aes.Context_CTR = --- + aes.init_ctr(&ctx, key[:], nonce[:]) + + for _ in 0 ..= options.rounds { + aes.xor_bytes_ctr(&ctx, buf, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + _benchmark_aes256_gcm :: proc( options: ^time.Benchmark_Options, allocator := context.allocator, diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin index 4d4c06bdc..c2fa2835c 100644 --- a/tests/core/crypto/test_core_crypto_aes.odin +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -12,8 +12,6 @@ import "core:crypto/sha2" test_aes :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - log.info("Testing AES") - impls := make([dynamic]aes.Implementation, 0, 2) defer delete(impls) append(&impls, aes.Implementation.Portable) @@ -29,7 +27,7 @@ test_aes :: proc(t: ^testing.T) { } test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { - log.infof("Testing AES-ECB/%v", impl) + log.debugf("Testing AES-ECB/%v", impl) test_vectors := []struct { key: string, @@ -136,7 +134,7 @@ test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { } test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { - log.infof("Testing AES-CTR/%v", impl) + log.debugf("Testing AES-CTR/%v", impl) test_vectors := []struct { key: string, @@ -200,7 +198,7 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { ctx: aes.Context_CTR key: [aes.KEY_SIZE_256]byte nonce: [aes.CTR_IV_SIZE]byte - aes.init_ctr(&ctx, key[:], nonce[:]) + aes.init_ctr(&ctx, key[:], nonce[:], impl) h_ctx: sha2.Context_512 sha2.init_512_256(&h_ctx) @@ -226,7 +224,7 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { } test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { - log.infof("Testing AES-GCM/%v", impl) + log.debugf("Testing AES-GCM/%v", impl) // NIST did a reorg of their site, so the source of the test vectors // is only available from an archive. The commented out tests are @@ -431,7 +429,7 @@ test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { testing.expectf( t, ok && dst_str == v.plaintext, - "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", + "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %v) instead", impl, v.plaintext, v.key, diff --git a/tests/core/crypto/test_core_crypto_ecc25519.odin b/tests/core/crypto/test_core_crypto_ecc25519.odin index baf4a1a38..fec4fa38e 100644 --- a/tests/core/crypto/test_core_crypto_ecc25519.odin +++ b/tests/core/crypto/test_core_crypto_ecc25519.odin @@ -58,9 +58,9 @@ test_sqrt_ratio_m1 :: proc(t: ^testing.T) { v_bytes, _ := hex.decode(transmute([]byte)(v.v), context.temp_allocator) r_bytes, _ := hex.decode(transmute([]byte)(v.r), context.temp_allocator) - u_ := transmute(^[32]byte)(raw_data(u_bytes)) - v_ := transmute(^[32]byte)(raw_data(v_bytes)) - r_ := transmute(^[32]byte)(raw_data(r_bytes)) + u_ := (^[32]byte)(raw_data(u_bytes)) + v_ := (^[32]byte)(raw_data(v_bytes)) + r_ := (^[32]byte)(raw_data(r_bytes)) u, vee, r: field.Tight_Field_Element field.fe_from_bytes(&u, u_) diff --git a/tests/core/crypto/test_core_crypto_kdf.odin b/tests/core/crypto/test_core_crypto_kdf.odin index 247529e65..c15dc2206 100644 --- a/tests/core/crypto/test_core_crypto_kdf.odin +++ b/tests/core/crypto/test_core_crypto_kdf.odin @@ -161,7 +161,7 @@ test_pbkdf2 :: proc(t: ^testing.T) { testing.expectf( t, dst_str == v.dk, - "HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", + "PBKDF2-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", algo_name, v.dk, v.password, diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin new file mode 100644 index 000000000..6e6c8152e --- /dev/null +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -0,0 +1,120 @@ +package test_core_ini + +import "base:runtime" +import "core:encoding/ini" +import "core:mem/virtual" +import "core:strings" +import "core:testing" + +@test +parse_ini :: proc(t: ^testing.T) { + ini_data := ` + [LOG] + level = "devel" + file = "/var/log/testing.log" + + [USER] + first_name = "John" + surname = "Smith" + ` + + m, err := ini.load_map_from_string(ini_data, context.allocator) + defer ini.delete_map(m) + + testing.expectf( + t, + strings.contains(m["LOG"]["level"], "devel"), + "Expected m[\"LOG\"][\"level\"] to be equal to 'devel' instead got %v", + m["LOG"]["level"], + ) + testing.expectf( + t, + strings.contains(m["LOG"]["file"], "/var/log/testing.log"), + "Expected m[\"LOG\"][\"file\"] to be equal to '/var/log/testing.log' instead got %v", + m["LOG"]["file"], + ) + testing.expectf( + t, + strings.contains(m["USER"]["first_name"], "John"), + "Expected m[\"USER\"][\"first_name\"] to be equal to 'John' instead got %v", + m["USER"]["first_name"], + ) + testing.expectf( + t, + strings.contains(m["USER"]["surname"], "Smith"), + "Expected m[\"USER\"][\"surname\"] to be equal to 'Smith' instead got %v", + m["USER"]["surname"], + ) + + testing.expectf(t, err == nil, "Expected `ini.load_map_from_string` to return a nil error, got %v", err) +} + +@test +ini_to_string :: proc(t: ^testing.T) { + m := ini.Map{ + "LEVEL" = { + "LOG" = "debug", + }, + } + + str := ini.save_map_to_string(m, context.allocator) + defer delete(str) + delete(m["LEVEL"]) + delete(m) + + testing.expectf( + t, + strings.contains(str, "[LEVEL]LOG = debug"), + "Expected `ini.save_map_to_string` to return a string equal to \"[LEVEL]LOG = debug\", got %v", + str, + ) +} + +@test +ini_iterator :: proc(t: ^testing.T) { + ini_data := ` + [LOG] + level = "devel" + file = "/var/log/testing.log" + + [USER] + first_name = "John" + surname = "Smith" + ` + + i := 0 + iterator := ini.iterator_from_string(ini_data) + for key, value in ini.iterate(&iterator) { + if strings.contains(key, "level") { + testing.expectf( + t, + strings.contains(value, "devel"), + "Expected 'level' to be equal to 'devel' instead got '%v'", + value, + ) + } else if strings.contains(key, "file") { + testing.expectf( + t, + strings.contains(value, "/var/log/testing.log"), + "Expected 'file' to be equal to '/var/log/testing.log' instead got '%v'", + value, + ) + } else if strings.contains(key, "first_name") { + testing.expectf( + t, + strings.contains(value, "John"), + "Expected 'first_name' to be equal to 'John' instead got '%v'", + value, + ) + } else if strings.contains(key, "surname") { + testing.expectf( + t, + strings.contains(value, "Smith"), + "Expected 'surname' to be equal to 'Smith' instead got '%v'", + value, + ) + } + i += 1 + } + testing.expectf(t, i == 4, "Expected to loop 4 times, only looped %v times", i) +} diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 92c050952..42ac9ce0f 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -3,6 +3,7 @@ package test_core_json import "core:encoding/json" import "core:testing" import "core:mem/virtual" +import "base:runtime" @test parse_json :: proc(t: ^testing.T) { @@ -348,6 +349,24 @@ unmarshal_json :: proc(t: ^testing.T) { } } +@test +unmarshal_empty_struct :: proc(t: ^testing.T) { + TestStruct :: struct {} + test := make(map[string]TestStruct) + input: = `{ + "test_1": {}, + "test_2": {} + }` + err := json.unmarshal(transmute([]u8)input, &test) + defer { + for k in test { + delete(k) + } + delete(test) + } + testing.expect(t, err == nil, "Expected empty struct to unmarshal without error") +} + @test surrogate :: proc(t: ^testing.T) { input := `+ + * 😃 - /` @@ -368,4 +387,60 @@ utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { val, err := json.parse_string(`"🐛✅"`) defer json.destroy_value(val) testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) +} + +@test +struct_with_ignore_tags :: proc(t: ^testing.T) { + My_Struct :: struct { + a: string `json:"-"`, + } + + my_struct := My_Struct{ + a = "test", + } + + my_struct_marshaled, marshal_err := json.marshal(my_struct) + defer delete(my_struct_marshaled) + + testing.expectf(t, marshal_err == nil, "Expected `json.marshal` to return nil error, got %v", marshal_err) + + my_struct_json := transmute(string)my_struct_marshaled + expected_json := `{}` + + testing.expectf(t, expected_json == my_struct_json, "Expected `json.marshal` to return %s, got %s", expected_json, my_struct_json) +} + +@test +map_with_integer_keys :: proc(t: ^testing.T) { + my_map := make(map[i32]string) + defer delete_map(my_map) + + my_map[-1] = "a" + my_map[0] = "b" + my_map[42] = "c" + my_map[99999999] = "d" + + marshaled_data, marshal_err := json.marshal(my_map) + defer delete(marshaled_data) + + testing.expectf(t, marshal_err == nil, "Expected `json.marshal` to return nil error, got %v", marshal_err) + + my_map2 := make(map[i32]string) + defer delete_map(my_map2) + + unmarshal_err := json.unmarshal(marshaled_data, &my_map2) + defer for key, item in my_map2 { + runtime.delete_string(item) + } + testing.expectf(t, unmarshal_err == nil, "Expected `json.unmarshal` to return nil, got %v", unmarshal_err) + + testing.expectf(t, len(my_map) == len(my_map2), "Expected %v map items to have been unmarshaled, got %v", len(my_map), len(my_map2)) + + for key, item in my_map { + testing.expectf(t, key in my_map2, "Expected key %v to be present in unmarshaled map", key) + + if key in my_map2 { + testing.expectf(t, runtime.string_eq(item, my_map2[key]), "Expected value %s to be present in unmarshaled map", key) + } + } } \ No newline at end of file diff --git a/tests/core/flags/test_core_flags.odin b/tests/core/flags/test_core_flags.odin index e32c6832c..f8305f2ea 100644 --- a/tests/core/flags/test_core_flags.odin +++ b/tests/core/flags/test_core_flags.odin @@ -1117,7 +1117,7 @@ test_os_handle :: proc(t: ^testing.T) { // Delete the file now that we're done. // // This is not done all the time, just in case the file is useful to debugging. - testing.expect_value(t, os.remove(TEMPORARY_FILENAME), os.ERROR_NONE) + testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) } TEMPORARY_FILENAME :: "test_core_flags_write_test_output_data" diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 49142e24d..3a1eb37e7 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -372,6 +372,22 @@ test_odin_value_export :: proc(t: ^testing.T) { } } +@(test) +leaking_struct_tag :: proc(t: ^testing.T) { + My_Struct :: struct { + names: [^]string `fmt:"v,name_count"`, + name_count: int, + } + + name := "hello?" + foo := My_Struct { + names = &name, + name_count = 1, + } + + check(t, "My_Struct{names = [\"hello?\"], name_count = 1}", "%v", foo) +} + @(private) check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { got := fmt.tprintf(format, ..args) diff --git a/tests/core/mem/test_mem_dynamic_pool.odin b/tests/core/mem/test_mem_dynamic_pool.odin new file mode 100644 index 000000000..80c973c68 --- /dev/null +++ b/tests/core/mem/test_mem_dynamic_pool.odin @@ -0,0 +1,80 @@ +package test_core_mem + +import "core:testing" +import "core:mem" + + +expect_pool_allocation :: proc(t: ^testing.T, expected_used_bytes, num_bytes, alignment: int) { + pool: mem.Dynamic_Pool + mem.dynamic_pool_init(pool = &pool, alignment = alignment) + pool_allocator := mem.dynamic_pool_allocator(&pool) + + element, err := mem.alloc(num_bytes, alignment, pool_allocator) + testing.expect(t, err == .None) + testing.expect(t, element != nil) + + expected_bytes_left := pool.block_size - expected_used_bytes + testing.expectf(t, pool.bytes_left == expected_bytes_left, + ` + Allocated data with size %v bytes, expected %v bytes left, got %v bytes left, off by %v bytes. + Pool: + block_size = %v + out_band_size = %v + alignment = %v + unused_blocks = %v + used_blocks = %v + out_band_allocations = %v + current_block = %v + current_pos = %v + bytes_left = %v + `, + num_bytes, expected_bytes_left, pool.bytes_left, expected_bytes_left - pool.bytes_left, + pool.block_size, + pool.out_band_size, + pool.alignment, + pool.unused_blocks, + pool.used_blocks, + pool.out_band_allocations, + pool.current_block, + pool.current_pos, + pool.bytes_left, + ) + + mem.dynamic_pool_destroy(&pool) + testing.expect(t, pool.used_blocks == nil) +} + +expect_pool_allocation_out_of_band :: proc(t: ^testing.T, num_bytes, out_band_size: int) { + testing.expect(t, num_bytes >= out_band_size, "Sanity check failed, your test call is flawed! Make sure that num_bytes >= out_band_size!") + + pool: mem.Dynamic_Pool + mem.dynamic_pool_init(pool = &pool, out_band_size = out_band_size) + pool_allocator := mem.dynamic_pool_allocator(&pool) + + element, err := mem.alloc(num_bytes, allocator = pool_allocator) + testing.expect(t, err == .None) + testing.expect(t, element != nil) + testing.expectf(t, pool.out_band_allocations != nil, + "Allocated data with size %v bytes, which is >= out_of_band_size and it should be in pool.out_band_allocations, but isn't!", + ) + + mem.dynamic_pool_destroy(&pool) + testing.expect(t, pool.out_band_allocations == nil) +} + +@(test) +test_dynamic_pool_alloc_aligned :: proc(t: ^testing.T) { + expect_pool_allocation(t, expected_used_bytes = 16, num_bytes = 16, alignment=8) +} + +@(test) +test_dynamic_pool_alloc_unaligned :: proc(t: ^testing.T) { + expect_pool_allocation(t, expected_used_bytes = 8, num_bytes=1, alignment=8) + expect_pool_allocation(t, expected_used_bytes = 16, num_bytes=9, alignment=8) +} + +@(test) +test_dynamic_pool_alloc_out_of_band :: proc(t: ^testing.T) { + expect_pool_allocation_out_of_band(t, num_bytes = 128, out_band_size = 128) + expect_pool_allocation_out_of_band(t, num_bytes = 129, out_band_size = 128) +} \ No newline at end of file diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 065090be3..8cd3b3917 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -30,12 +30,14 @@ download_assets :: proc() { @(require) import "mem" @(require) import "net" @(require) import "odin" +@(require) import "os" @(require) import "path/filepath" @(require) import "reflect" @(require) import "runtime" @(require) import "slice" @(require) import "strconv" @(require) import "strings" +@(require) import "sys/windows" @(require) import "text/i18n" @(require) import "text/match" @(require) import "thread" diff --git a/tests/core/os/dir/alink.txt b/tests/core/os/dir/alink.txt new file mode 120000 index 000000000..1891a26c0 --- /dev/null +++ b/tests/core/os/dir/alink.txt @@ -0,0 +1 @@ +./a.txt \ No newline at end of file diff --git a/tests/core/os/dir/b.txt b/tests/core/os/dir/b.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/os/dir/sub/.gitkeep b/tests/core/os/dir/sub/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/os/os.odin b/tests/core/os/os.odin new file mode 100644 index 000000000..97ceaeb1e --- /dev/null +++ b/tests/core/os/os.odin @@ -0,0 +1,35 @@ +package tests_core_os + +import "core:os" +import "core:slice" + +import "core:testing" + +@(test) +read_dir :: proc(t: ^testing.T) { + fd, err := os.open(#directory + "/dir") + testing.expect_value(t, err, nil) + defer os.close(fd) + + dir, err2 := os.read_dir(fd, -1) + testing.expect_value(t, err2, nil) + defer os.file_info_slice_delete(dir) + + slice.sort_by_key(dir, proc(fi: os.File_Info) -> string { return fi.name }) + + testing.expect_value(t, len(dir), 3) + + testing.expect_value(t, dir[0].name, "alink.txt") + testing.expect(t, !dir[0].is_dir, "is a directory") + when ODIN_OS == .Windows { + testing.expect(t, dir[0].mode & os.File_Mode_Sym_Link != 0, "not a symlink") + } else { + testing.expect(t, os.S_ISLNK(auto_cast dir[0].mode), "not a symlink") + } + + testing.expect_value(t, dir[1].name, "b.txt") + + testing.expect_value(t, dir[2].name, "sub") + testing.expect(t, dir[2].is_dir, "is not a directory") +} + diff --git a/tests/core/sys/windows/test_kernel32.odin b/tests/core/sys/windows/test_kernel32.odin new file mode 100644 index 000000000..81331fc9f --- /dev/null +++ b/tests/core/sys/windows/test_kernel32.odin @@ -0,0 +1,24 @@ +//+build windows +package test_core_sys_windows + +import "base:intrinsics" +import win32 "core:sys/windows" +import "core:testing" + +@(test) +lcid_to_local :: proc(t: ^testing.T) { + lcid: win32.LCID = win32.MAKELANGID(0x09, win32.SUBLANG_DEFAULT) + wname: [512]win32.WCHAR + cc := win32.LCIDToLocaleName(lcid, &wname[0], len(wname) - 1, 0) + testing.expectf(t, cc == 6, "%#x (should be: %#x)", u32(cc), 6) + if cc == 0 {return} + str, err := win32.wstring_to_utf8(win32.wstring(&wname), int(cc)) + testing.expectf(t, err == .None, "%v (should be: %x)", err, 0) + exp :: "en-US" + testing.expectf(t, str == exp, "%v (should be: %v)", str, exp) + + cc2 := win32.LocaleNameToLCID(L(exp), 0) + testing.expectf(t, cc2 == 0x0409, "%#x (should be: %#x)", u32(cc2), 0x0409) + + //fmt.printfln("%0X", lcid) +} diff --git a/tests/core/sys/windows/test_ole32.odin b/tests/core/sys/windows/test_ole32.odin new file mode 100644 index 000000000..30bf5bc80 --- /dev/null +++ b/tests/core/sys/windows/test_ole32.odin @@ -0,0 +1,62 @@ +//+build windows +package test_core_sys_windows + +import "base:intrinsics" +import win32 "core:sys/windows" +import "core:testing" + +@(test) +string_from_clsid :: proc(t: ^testing.T) { + p: win32.LPOLESTR + hr := win32.StringFromCLSID(win32.CLSID_FileOpenDialog, &p) + defer if p != nil {win32.CoTaskMemFree(p)} + + testing.expectf(t, win32.SUCCEEDED(hr), "%x (should be: %x)", u32(hr), 0) + testing.expectf(t, p != nil, "%v is nil", p) + + str, err := win32.wstring_to_utf8(p, 38) + testing.expectf(t, err == .None, "%v (should be: %x)", err, 0) + exp :: "{DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7}" + testing.expectf(t, str == exp, "%v (should be: %v)", str, exp) +} + +@(test) +clsid_from_string :: proc(t: ^testing.T) { + iid: win32.IID + hr := win32.CLSIDFromString(L("{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}"), &iid) + testing.expectf(t, win32.SUCCEEDED(hr), "%x (should be: %x)", u32(hr), 0) + exp := win32.FOLDERID_NetworkFolder + testing.expectf(t, iid == exp, "%v (should be: %v)", iid, exp) +} + +@(test) +string_from_iid :: proc(t: ^testing.T) { + p: win32.LPOLESTR + hr := win32.StringFromIID(win32.IID_IFileDialog, &p) + defer if p != nil {win32.CoTaskMemFree(p)} + + testing.expectf(t, win32.SUCCEEDED(hr), "%x (should be: %x)", u32(hr), 0) + testing.expectf(t, p != nil, "%v is nil", p) + + str, err := win32.wstring_to_utf8(p, 40) + testing.expectf(t, err == .None, "%v (should be: %x)", err, 0) + exp :: "{42F85136-DB7E-439C-85F1-E4075D135FC8}" + testing.expectf(t, str == exp, "%v (should be: %v)", str, exp) +} + +@(test) +iid_from_string :: proc(t: ^testing.T) { + iid: win32.IID + hr := win32.IIDFromString(L("{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}"), &iid) + testing.expectf(t, win32.SUCCEEDED(hr), "%x (should be: %x)", u32(hr), 0) + exp := win32.FOLDERID_NetworkFolder + testing.expectf(t, iid == exp, "%v (should be: %v)", iid, exp) +} + +@(test) +verify_coinit :: proc(t: ^testing.T) { + expect_value(t, win32.COINIT.MULTITHREADED, 0x00000000) + expect_value(t, win32.COINIT.APARTMENTTHREADED, 0x00000002) + expect_value(t, win32.COINIT.DISABLE_OLE1DDE, 0x00000004) + expect_value(t, win32.COINIT.SPEED_OVER_MEMORY, 0x00000008) +} diff --git a/tests/core/sys/windows/test_windows.odin b/tests/core/sys/windows/test_windows.odin new file mode 100644 index 000000000..724b1b7af --- /dev/null +++ b/tests/core/sys/windows/test_windows.odin @@ -0,0 +1,46 @@ +//+build windows +package test_core_sys_windows + +import "base:intrinsics" +import "base:runtime" +import win32 "core:sys/windows" +import "core:testing" + +L :: intrinsics.constant_utf16_cstring +expectf :: testing.expectf + +@(private) +expect_size :: proc(t: ^testing.T, $act: typeid, exp: int, loc := #caller_location) { + expectf(t, size_of(act) == exp, "size_of(%v) should be %d was %d", typeid_of(act), exp, size_of(act), loc = loc) +} + +@(private) +expect_value :: proc(t: ^testing.T, #any_int act: u32, #any_int exp: u32, loc := #caller_location) { + expectf(t, act == exp, "0x%8X (should be: 0x%8X)", act, exp, loc = loc) +} + +@(private) +expect_value_64 :: proc(t: ^testing.T, #any_int act: u64, #any_int exp: u64, loc := #caller_location) { + expectf(t, act == exp, "0x%8X (should be: 0x%8X)", act, exp, loc = loc) +} + +@(private) +expect_value_int :: proc(t: ^testing.T, act, exp: int, loc := #caller_location) { + expectf(t, act == exp, "0x%8X (should be: 0x%8X)", act, exp, loc = loc) +} + +@(private) +expect_value_uintptr :: proc(t: ^testing.T, act: uintptr, exp: int, loc := #caller_location) { + expectf(t, act == uintptr(exp), "0x%8X (should be: 0x%8X)", act, uintptr(exp), loc = loc) +} + +@(private) +expect_value_str :: proc(t: ^testing.T, wact, wexp: win32.wstring, loc := #caller_location) { + act, exp: string + err: runtime.Allocator_Error + act, err = win32.wstring_to_utf8(wact, 16) + expectf(t, err == .None, "0x%8X (should be: 0x%8X)", err, 0, loc = loc) + exp, err = win32.wstring_to_utf8(wexp, 16) + expectf(t, err == .None, "0x%8X (should be: 0x%8X)", err, 0, loc = loc) + expectf(t, act == exp, "0x%8X (should be: 0x%8X)", act, exp, loc = loc) +} diff --git a/tests/core/sys/windows/test_windows_generated.odin b/tests/core/sys/windows/test_windows_generated.odin new file mode 100644 index 000000000..13d09f4e1 --- /dev/null +++ b/tests/core/sys/windows/test_windows_generated.odin @@ -0,0 +1,699 @@ +//+build windows +package test_core_sys_windows // generated by win32gen + +import "core:testing" +import win32 "core:sys/windows" + +@(test) +verify_win32_type_sizes :: proc(t: ^testing.T) { + // minwindef.h + expect_size(t, win32.ULONG, 4) + expect_size(t, win32.PULONG, 8) + expect_size(t, win32.USHORT, 2) + expect_size(t, win32.PUSHORT, 8) + expect_size(t, win32.UCHAR, 1) + expect_size(t, win32.DWORD, 4) + expect_size(t, win32.BOOL, 4) + expect_size(t, win32.BYTE, 1) + expect_size(t, win32.WORD, 2) + expect_size(t, win32.PBOOL, 8) + expect_size(t, win32.LPBOOL, 8) + expect_size(t, win32.PBYTE, 8) + expect_size(t, win32.LPBYTE, 8) + expect_size(t, win32.PINT, 8) + expect_size(t, win32.LPINT, 8) + expect_size(t, win32.LPWORD, 8) + expect_size(t, win32.PDWORD, 8) + expect_size(t, win32.LPDWORD, 8) + expect_size(t, win32.LPVOID, 8) + expect_size(t, win32.LPCVOID, 8) + expect_size(t, win32.INT, 4) + expect_size(t, win32.UINT, 4) + expect_size(t, win32.PUINT, 8) + expect_size(t, win32.UINT_PTR, 8) + expect_size(t, win32.LONG_PTR, 8) + expect_size(t, win32.HANDLE, 8) + expect_size(t, win32.WPARAM, 8) + expect_size(t, win32.LPARAM, 8) + expect_size(t, win32.LRESULT, 8) + expect_size(t, win32.LPHANDLE, 8) + expect_size(t, win32.HGLOBAL, 8) + expect_size(t, win32.ATOM, 2) + expect_size(t, win32.HKEY, 8) + expect_size(t, win32.PHKEY, 8) + expect_size(t, win32.HINSTANCE, 8) + expect_size(t, win32.HMODULE, 8) + expect_size(t, win32.HRGN, 8) + expect_size(t, win32.HRSRC, 8) + // windef.h + expect_size(t, win32.HWND, 8) + expect_size(t, win32.HHOOK, 8) + expect_size(t, win32.HGDIOBJ, 8) + expect_size(t, win32.HBITMAP, 8) + expect_size(t, win32.HPALETTE, 8) + expect_size(t, win32.HBRUSH, 8) + expect_size(t, win32.HPEN, 8) + expect_size(t, win32.HFONT, 8) + expect_size(t, win32.HICON, 8) + expect_size(t, win32.HMENU, 8) + expect_size(t, win32.HCURSOR, 8) + expect_size(t, win32.COLORREF, 4) + expect_size(t, win32.RECT, 16) + expect_size(t, win32.POINT, 8) + expect_size(t, win32.SIZE, 8) + // wtypes.h + expect_size(t, win32.DECIMAL, 16) + // fileapi.h + expect_size(t, win32.WIN32_FILE_ATTRIBUTE_DATA, 36) + // libloaderapi.h + expect_size(t, win32.ENUMRESNAMEPROCW, 8) + expect_size(t, win32.ENUMRESTYPEPROCW, 8) + // minwinbase.h + expect_size(t, win32.SYSTEMTIME, 16) + expect_size(t, win32.WIN32_FIND_DATAW, 592) + expect_size(t, win32.CRITICAL_SECTION, 40) + expect_size(t, win32.REASON_CONTEXT, 32) + // guiddef.h + expect_size(t, win32.GUID, 16) + expect_size(t, win32.IID, 16) + expect_size(t, win32.CLSID, 16) + // combaseapi.h + expect_size(t, win32.SCODE, 4) + // commdlg.h + expect_size(t, win32.OPENFILENAMEW, 152) + // wtypesbase.h + expect_size(t, win32.OLECHAR, 2) +} + +@(test) +verify_macros :: proc(t: ^testing.T) { + // minwindef.h + expect_value(t, win32.MAKEWORD(1, 2), 0x00000201) + expect_value(t, win32.MAKEWORD(0x1111, 0x2222), 0x00002211) + expect_value(t, win32.MAKELONG(1, 2), 0x00020001) + expect_value(t, win32.MAKELONG(0x1111, 0x2222), 0x22221111) + expect_value(t, win32.LOWORD(0x12345678), 0x00005678) + expect_value(t, win32.HIWORD(0x12345678), 0x00001234) + expect_value(t, u32(win32.LOBYTE(0x1234)), 0x00000034) + expect_value(t, u32(win32.HIBYTE(0x1234)), 0x00000012) + // winuser.h + expect_value(t, win32.MAKEWPARAM(1, 2), 0x00020001) + expect_value(t, win32.MAKEWPARAM(0x1111, 0x2222), 0x22221111) + expect_value(t, win32.MAKELPARAM(1, 2), 0x00020001) + expect_value(t, win32.MAKELPARAM(0x1111, 0x2222), 0x22221111) + expect_value(t, win32.MAKELRESULT(1, 2), 0x00020001) + expect_value(t, win32.MAKELRESULT(0x1111, 0x2222), 0x22221111) + // winnt.h + expect_value(t, win32.MAKELCID(1, 2), 0x00020001) + expect_value(t, win32.MAKELCID(0x1111, 0x2222), 0x22221111) + expect_value(t, win32.MAKELANGID(1, 2), 0x00000801) + expect_value(t, win32.MAKELANGID(0x111, 0x222), 0x00088911) + expect_value(t, win32.LANGIDFROMLCID(0x12345678), 0x00005678) +} + +@(test) +verify_winnt :: proc(t: ^testing.T) { + // winnt.h + expect_size(t, win32.CHAR, 1) + expect_size(t, win32.SHORT, 2) + expect_size(t, win32.LONG, 4) + expect_size(t, win32.INT, 4) + expect_size(t, win32.WCHAR, 2) + expect_size(t, win32.ULONGLONG, 8) + expect_size(t, win32.LARGE_INTEGER, 8) + expect_size(t, win32.PLARGE_INTEGER, 8) + expect_size(t, win32.ULARGE_INTEGER, 8) + expect_size(t, win32.PULARGE_INTEGER, 8) + expect_size(t, win32.BOOLEAN, 1) + expect_size(t, win32.HANDLE, 8) + expect_size(t, win32.PHANDLE, 8) + expect_size(t, win32.HRESULT, 4) + expect_size(t, win32.LCID, 4) + expect_size(t, win32.LANGID, 2) + expect_size(t, win32.LUID, 8) + expect_size(t, win32.SECURITY_INFORMATION, 4) + expect_size(t, win32.ACCESS_MASK, 4) + expect_size(t, win32.REGSAM, 4) + expect_value(t, win32.LANG_NEUTRAL, 0x00000000) + expect_value(t, win32.LANG_INVARIANT, 0x0000007F) + expect_value(t, win32.SUBLANG_NEUTRAL, 0x00000000) + expect_value(t, win32.SUBLANG_DEFAULT, 0x00000001) +} + +@(test) +verify_winuser :: proc(t: ^testing.T) { + // winuser.h + expect_size(t, win32.USEROBJECTFLAGS, 12) + expect_size(t, win32.MSG, 48) + expect_size(t, win32.WINDOWPOS, 40) + expect_size(t, win32.ACCEL, 6) + expect_size(t, win32.MENUITEMINFOW, 80) + expect_size(t, win32.PAINTSTRUCT, 72) + expect_size(t, win32.CREATESTRUCTW, 80) + expect_size(t, win32.WINDOWPLACEMENT, 44) + expect_size(t, win32.MOUSEINPUT, 32) + expect_size(t, win32.KEYBDINPUT, 24) + expect_size(t, win32.HARDWAREINPUT, 8) + expect_size(t, win32.INPUT, 40) + expect_size(t, win32.ICONINFOEXW, 1080) + expect_size(t, win32.CURSORINFO, 24) + expect_size(t, win32.WINDOWINFO, 60) + expect_size(t, win32.RAWINPUTHEADER, 24) + expect_size(t, win32.RAWMOUSE, 24) + expect_size(t, win32.RAWKEYBOARD, 16) + expect_size(t, win32.RAWINPUT, 48) + expect_size(t, win32.RAWINPUTDEVICE, 16) + expect_size(t, win32.RAWINPUTDEVICELIST, 16) + expect_size(t, win32.RID_DEVICE_INFO_HID, 16) + expect_size(t, win32.RID_DEVICE_INFO_KEYBOARD, 24) + expect_size(t, win32.RID_DEVICE_INFO_MOUSE, 16) + expect_size(t, win32.RID_DEVICE_INFO, 32) + expect_value(t, win32.GET_RAWINPUT_CODE_WPARAM(0x12345678), 0x00000078) + expect_size(t, win32.DRAWTEXTPARAMS, 20) + expect_size(t, win32.BSMINFO, 32) + expect_value(t, win32.BROADCAST_QUERY_DENY, 0x424D5144) + expect_value_64(t, u64(win32.HWND_BROADCAST), 0x0000FFFF) + expect_value_64(t, u64(win32.HWND_MESSAGE), 0xFFFFFFFFFFFFFFFD) + expect_value_64(t, uintptr(win32.MAKEINTRESOURCEW(1)), 0x00000001) + expect_value_64(t, uintptr(win32.MAKEINTRESOURCEW(0x12345678)), 0x00005678) + expect_value_64(t, uintptr(win32.RT_CURSOR), 0x00000001) + expect_value_64(t, uintptr(win32.RT_BITMAP), 0x00000002) + expect_value_64(t, uintptr(win32.RT_ICON), 0x00000003) + expect_value_64(t, uintptr(win32.RT_MENU), 0x00000004) + expect_value_64(t, uintptr(win32.RT_DIALOG), 0x00000005) + expect_value_64(t, uintptr(win32.RT_STRING), 0x00000006) + expect_value_64(t, uintptr(win32.RT_FONTDIR), 0x00000007) + expect_value_64(t, uintptr(win32.RT_FONT), 0x00000008) + expect_value_64(t, uintptr(win32.RT_ACCELERATOR), 0x00000009) + expect_value_64(t, uintptr(win32.RT_RCDATA), 0x0000000A) + expect_value_64(t, uintptr(win32.RT_MESSAGETABLE), 0x0000000B) + expect_value_64(t, uintptr(win32.RT_GROUP_CURSOR), 0x0000000C) + expect_value_64(t, uintptr(win32.RT_GROUP_ICON), 0x0000000E) + expect_value_64(t, uintptr(win32.RT_VERSION), 0x00000010) + expect_value_64(t, uintptr(win32.RT_DLGINCLUDE), 0x00000011) + expect_value_64(t, uintptr(win32.RT_PLUGPLAY), 0x00000013) + expect_value_64(t, uintptr(win32.RT_VXD), 0x00000014) + expect_value_64(t, uintptr(win32.RT_ANICURSOR), 0x00000015) + expect_value_64(t, uintptr(win32.RT_ANIICON), 0x00000016) + expect_value_64(t, uintptr(win32.RT_MANIFEST), 0x00000018) + expect_value_64(t, uintptr(win32.CREATEPROCESS_MANIFEST_RESOURCE_ID), 0x00000001) + expect_value_64(t, uintptr(win32.ISOLATIONAWARE_MANIFEST_RESOURCE_ID), 0x00000002) + expect_value_64(t, uintptr(win32.ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID), 0x00000003) + expect_value_64(t, uintptr(win32.ISOLATIONPOLICY_MANIFEST_RESOURCE_ID), 0x00000004) + expect_value_64(t, uintptr(win32.ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID), 0x00000005) + expect_value_64(t, uintptr(win32.MINIMUM_RESERVED_MANIFEST_RESOURCE_ID), 0x00000001) + expect_value_64(t, uintptr(win32.MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID), 0x00000010) + expect_value(t, win32.SM_CXICON, 0x0000000B) + expect_value(t, win32.SM_CYICON, 0x0000000C) + expect_value(t, win32.LR_DEFAULTCOLOR, 0x00000000) + expect_value(t, win32.LR_MONOCHROME, 0x00000001) + expect_value(t, win32.LR_COLOR, 0x00000002) + expect_value(t, win32.LR_COPYRETURNORG, 0x00000004) + expect_value(t, win32.LR_COPYDELETEORG, 0x00000008) + expect_value(t, win32.LR_LOADFROMFILE, 0x00000010) + expect_value(t, win32.LR_LOADTRANSPARENT, 0x00000020) + expect_value(t, win32.LR_DEFAULTSIZE, 0x00000040) + expect_value(t, win32.LR_VGACOLOR, 0x00000080) + expect_value(t, win32.LR_LOADMAP3DCOLORS, 0x00001000) + expect_value(t, win32.LR_CREATEDIBSECTION, 0x00002000) + expect_value(t, win32.LR_COPYFROMRESOURCE, 0x00004000) + expect_value(t, win32.LR_SHARED, 0x00008000) + expect_value(t, win32.NIM_ADD, 0x00000000) + expect_value(t, win32.NIM_MODIFY, 0x00000001) + expect_value(t, win32.NIM_DELETE, 0x00000002) + expect_value(t, win32.NIM_SETFOCUS, 0x00000003) + expect_value(t, win32.NIM_SETVERSION, 0x00000004) + expect_value(t, win32.NIF_MESSAGE, 0x00000001) + expect_value(t, win32.NIF_ICON, 0x00000002) + expect_value(t, win32.NIF_TIP, 0x00000004) + expect_value(t, win32.NIF_STATE, 0x00000008) + expect_value(t, win32.NIF_INFO, 0x00000010) + expect_value(t, win32.NIF_GUID, 0x00000020) + expect_value(t, win32.NIF_REALTIME, 0x00000040) + expect_value(t, win32.NIF_SHOWTIP, 0x00000080) + expect_value(t, win32.MF_INSERT, 0x00000000) + expect_value(t, win32.MF_CHANGE, 0x00000080) + expect_value(t, win32.MF_APPEND, 0x00000100) + expect_value(t, win32.MF_DELETE, 0x00000200) + expect_value(t, win32.MF_REMOVE, 0x00001000) + expect_value(t, win32.MF_BYCOMMAND, 0x00000000) + expect_value(t, win32.MF_BYPOSITION, 0x00000400) + expect_value(t, win32.MF_SEPARATOR, 0x00000800) + expect_value(t, win32.MF_ENABLED, 0x00000000) + expect_value(t, win32.MF_GRAYED, 0x00000001) + expect_value(t, win32.MF_DISABLED, 0x00000002) + expect_value(t, win32.MF_UNCHECKED, 0x00000000) + expect_value(t, win32.MF_CHECKED, 0x00000008) + expect_value(t, win32.MF_USECHECKBITMAPS, 0x00000200) + expect_value(t, win32.MF_STRING, 0x00000000) + expect_value(t, win32.MF_BITMAP, 0x00000004) + expect_value(t, win32.MF_OWNERDRAW, 0x00000100) + expect_value(t, win32.MF_POPUP, 0x00000010) + expect_value(t, win32.MF_MENUBARBREAK, 0x00000020) + expect_value(t, win32.MF_MENUBREAK, 0x00000040) + expect_value(t, win32.MF_UNHILITE, 0x00000000) + expect_value(t, win32.MF_HILITE, 0x00000080) + expect_value(t, win32.MF_DEFAULT, 0x00001000) + expect_value(t, win32.MF_SYSMENU, 0x00002000) + expect_value(t, win32.MF_HELP, 0x00004000) + expect_value(t, win32.MF_RIGHTJUSTIFY, 0x00004000) + expect_value(t, win32.MF_MOUSESELECT, 0x00008000) + expect_value(t, win32.MF_END, 0x00000080) + expect_value(t, win32.MFS_GRAYED, 0x00000003) + expect_value(t, win32.MFS_DISABLED, 0x00000003) + expect_value(t, win32.MFS_CHECKED, 0x00000008) + expect_value(t, win32.MFS_HILITE, 0x00000080) + expect_value(t, win32.MFS_ENABLED, 0x00000000) + expect_value(t, win32.MFS_UNCHECKED, 0x00000000) + expect_value(t, win32.MFS_UNHILITE, 0x00000000) + expect_value(t, win32.MFS_DEFAULT, 0x00001000) + expect_value(t, win32.TPM_LEFTBUTTON, 0x00000000) + expect_value(t, win32.TPM_RIGHTBUTTON, 0x00000002) + expect_value(t, win32.TPM_LEFTALIGN, 0x00000000) + expect_value(t, win32.TPM_CENTERALIGN, 0x00000004) + expect_value(t, win32.TPM_RIGHTALIGN, 0x00000008) + expect_value(t, win32.TPM_TOPALIGN, 0x00000000) + expect_value(t, win32.TPM_VCENTERALIGN, 0x00000010) + expect_value(t, win32.TPM_BOTTOMALIGN, 0x00000020) + expect_value(t, win32.TPM_HORIZONTAL, 0x00000000) + expect_value(t, win32.TPM_VERTICAL, 0x00000040) + expect_value(t, win32.TPM_NONOTIFY, 0x00000080) + expect_value(t, win32.TPM_RETURNCMD, 0x00000100) + expect_value(t, win32.TPM_RECURSE, 0x00000001) + expect_value(t, win32.TPM_HORPOSANIMATION, 0x00000400) + expect_value(t, win32.TPM_HORNEGANIMATION, 0x00000800) + expect_value(t, win32.TPM_VERPOSANIMATION, 0x00001000) + expect_value(t, win32.TPM_VERNEGANIMATION, 0x00002000) + expect_value(t, win32.TPM_NOANIMATION, 0x00004000) + expect_value(t, win32.TPM_LAYOUTRTL, 0x00008000) + expect_value(t, win32.TPM_WORKAREA, 0x00010000) + expect_value(t, win32.MIIM_STATE, 0x00000001) + expect_value(t, win32.MIIM_ID, 0x00000002) + expect_value(t, win32.MIIM_SUBMENU, 0x00000004) + expect_value(t, win32.MIIM_CHECKMARKS, 0x00000008) + expect_value(t, win32.MIIM_TYPE, 0x00000010) + expect_value(t, win32.MIIM_DATA, 0x00000020) + expect_value(t, win32.MIIM_STRING, 0x00000040) + expect_value(t, win32.MIIM_BITMAP, 0x00000080) + expect_value(t, win32.MIIM_FTYPE, 0x00000100) + expect_value(t, win32.ANSI_CHARSET, 0x00000000) + expect_value(t, win32.DEFAULT_CHARSET, 0x00000001) + expect_value(t, win32.SYMBOL_CHARSET, 0x00000002) + expect_value(t, win32.SHIFTJIS_CHARSET, 0x00000080) + expect_value(t, win32.HANGEUL_CHARSET, 0x00000081) + expect_value(t, win32.HANGUL_CHARSET, 0x00000081) + expect_value(t, win32.GB2312_CHARSET, 0x00000086) + expect_value(t, win32.CHINESEBIG5_CHARSET, 0x00000088) + expect_value(t, win32.OEM_CHARSET, 0x000000FF) + expect_value(t, win32.JOHAB_CHARSET, 0x00000082) + expect_value(t, win32.HEBREW_CHARSET, 0x000000B1) + expect_value(t, win32.ARABIC_CHARSET, 0x000000B2) + expect_value(t, win32.GREEK_CHARSET, 0x000000A1) + expect_value(t, win32.TURKISH_CHARSET, 0x000000A2) + expect_value(t, win32.VIETNAMESE_CHARSET, 0x000000A3) + expect_value(t, win32.THAI_CHARSET, 0x000000DE) + expect_value(t, win32.EASTEUROPE_CHARSET, 0x000000EE) + expect_value(t, win32.RUSSIAN_CHARSET, 0x000000CC) + expect_value(t, win32.MAC_CHARSET, 0x0000004D) + expect_value(t, win32.BALTIC_CHARSET, 0x000000BA) + expect_value(t, win32.FS_LATIN1, 0x00000001) + expect_value(t, win32.FS_LATIN2, 0x00000002) + expect_value(t, win32.FS_CYRILLIC, 0x00000004) + expect_value(t, win32.FS_GREEK, 0x00000008) + expect_value(t, win32.FS_TURKISH, 0x00000010) + expect_value(t, win32.FS_HEBREW, 0x00000020) + expect_value(t, win32.FS_ARABIC, 0x00000040) + expect_value(t, win32.FS_BALTIC, 0x00000080) + expect_value(t, win32.FS_VIETNAMESE, 0x00000100) + expect_value(t, win32.FS_THAI, 0x00010000) + expect_value(t, win32.FS_JISJAPAN, 0x00020000) + expect_value(t, win32.FS_CHINESESIMP, 0x00040000) + expect_value(t, win32.FS_WANSUNG, 0x00080000) + expect_value(t, win32.FS_CHINESETRAD, 0x00100000) + expect_value(t, win32.FS_JOHAB, 0x00200000) + expect_value(t, win32.FS_SYMBOL, 0x80000000) + expect_value(t, win32.OUT_DEFAULT_PRECIS, 0x00000000) + expect_value(t, win32.OUT_STRING_PRECIS, 0x00000001) + expect_value(t, win32.OUT_CHARACTER_PRECIS, 0x00000002) + expect_value(t, win32.OUT_STROKE_PRECIS, 0x00000003) + expect_value(t, win32.OUT_TT_PRECIS, 0x00000004) + expect_value(t, win32.OUT_DEVICE_PRECIS, 0x00000005) + expect_value(t, win32.OUT_RASTER_PRECIS, 0x00000006) + expect_value(t, win32.OUT_TT_ONLY_PRECIS, 0x00000007) + expect_value(t, win32.OUT_OUTLINE_PRECIS, 0x00000008) + expect_value(t, win32.OUT_SCREEN_OUTLINE_PRECIS, 0x00000009) + expect_value(t, win32.OUT_PS_ONLY_PRECIS, 0x0000000A) + expect_value(t, win32.CLIP_DEFAULT_PRECIS, 0x00000000) + expect_value(t, win32.CLIP_CHARACTER_PRECIS, 0x00000001) + expect_value(t, win32.CLIP_STROKE_PRECIS, 0x00000002) + expect_value(t, win32.CLIP_MASK, 0x0000000F) + expect_value(t, win32.CLIP_LH_ANGLES, 0x00000010) + expect_value(t, win32.CLIP_TT_ALWAYS, 0x00000020) + expect_value(t, win32.CLIP_DFA_DISABLE, 0x00000040) + expect_value(t, win32.CLIP_EMBEDDED, 0x00000080) + expect_value(t, win32.DEFAULT_QUALITY, 0x00000000) + expect_value(t, win32.DRAFT_QUALITY, 0x00000001) + expect_value(t, win32.PROOF_QUALITY, 0x00000002) + expect_value(t, win32.NONANTIALIASED_QUALITY, 0x00000003) + expect_value(t, win32.ANTIALIASED_QUALITY, 0x00000004) + expect_value(t, win32.CLEARTYPE_QUALITY, 0x00000005) + expect_value(t, win32.CLEARTYPE_NATURAL_QUALITY, 0x00000006) + expect_value(t, win32.DEFAULT_PITCH, 0x00000000) + expect_value(t, win32.FIXED_PITCH, 0x00000001) + expect_value(t, win32.VARIABLE_PITCH, 0x00000002) + expect_value(t, win32.MONO_FONT, 0x00000008) + expect_value(t, win32.FF_DONTCARE, 0x00000000) + expect_value(t, win32.FF_ROMAN, 0x00000010) + expect_value(t, win32.FF_SWISS, 0x00000020) + expect_value(t, win32.FF_MODERN, 0x00000030) + expect_value(t, win32.FF_SCRIPT, 0x00000040) + expect_value(t, win32.FF_DECORATIVE, 0x00000050) +} + +@(test) +verify_gdi32 :: proc(t: ^testing.T) { + // wingdi.h + expect_size(t, win32.DEVMODEW, 220) + expect_size(t, win32.RGBQUAD, 4) + expect_size(t, win32.PIXELFORMATDESCRIPTOR, 40) + expect_size(t, win32.BITMAPINFOHEADER, 40) + expect_size(t, win32.BITMAP, 32) + expect_size(t, win32.BITMAPV5HEADER, 124) + expect_size(t, win32.CIEXYZTRIPLE, 36) + expect_size(t, win32.CIEXYZ, 12) + expect_size(t, win32.FXPT2DOT30, 4) + expect_size(t, win32.TEXTMETRICW, 60) + expect_size(t, win32.POINTFLOAT, 8) + expect_size(t, win32.GLYPHMETRICSFLOAT, 24) + expect_size(t, win32.PALETTEENTRY, 4) + expect_size(t, win32.DESIGNVECTOR, 72) + expect_value(t, win32.LF_FACESIZE, 0x00000020) + expect_value(t, win32.LF_FULLFACESIZE, 0x00000040) + expect_size(t, win32.LOGFONTW, 92) + expect_size(t, win32.ENUMLOGFONTW, 284) + expect_size(t, win32.ENUMLOGFONTEXW, 348) + expect_size(t, win32.ENUMLOGFONTEXDVW, 420) + expect_size(t, win32.NEWTEXTMETRICW, 76) + expect_size(t, win32.LAYERPLANEDESCRIPTOR, 32) + expect_size(t, win32.COLOR16, 2) + expect_size(t, win32.TRIVERTEX, 16) + expect_size(t, win32.GRADIENT_TRIANGLE, 12) + expect_size(t, win32.GRADIENT_RECT, 8) + expect_size(t, win32.BLENDFUNCTION, 4) + expect_size(t, win32.DISPLAY_DEVICEW, 840) + expect_value(t, win32.AC_SRC_OVER, 0x00000000) + expect_value(t, win32.AC_SRC_ALPHA, 0x00000001) + expect_value(t, win32.RGB(12, 34, 56), 0x0038220C) + expect_value(t, win32.PALETTERGB(12, 34, 56), 0x0238220C) + expect_value(t, win32.PALETTEINDEX(123), 0x0100007B) + expect_value(t, win32.GRADIENT_FILL_RECT_H, 0x00000000) + expect_value(t, win32.GRADIENT_FILL_RECT_V, 0x00000001) + expect_value(t, win32.GRADIENT_FILL_TRIANGLE, 0x00000002) + expect_value(t, win32.BS_SOLID, 0x00000000) + expect_value(t, win32.BS_NULL, 0x00000001) + expect_value(t, win32.BS_HOLLOW, 0x00000001) + expect_value(t, win32.BS_HATCHED, 0x00000002) + expect_value(t, win32.BS_PATTERN, 0x00000003) + expect_value(t, win32.BS_INDEXED, 0x00000004) + expect_value(t, win32.BS_DIBPATTERN, 0x00000005) + expect_value(t, win32.BS_DIBPATTERNPT, 0x00000006) + expect_value(t, win32.BS_PATTERN8X8, 0x00000007) + expect_value(t, win32.BS_DIBPATTERN8X8, 0x00000008) + expect_value(t, win32.BS_MONOPATTERN, 0x00000009) + expect_value(t, win32.HS_HORIZONTAL, 0x00000000) + expect_value(t, win32.HS_VERTICAL, 0x00000001) + expect_value(t, win32.HS_FDIAGONAL, 0x00000002) + expect_value(t, win32.HS_BDIAGONAL, 0x00000003) + expect_value(t, win32.HS_CROSS, 0x00000004) + expect_value(t, win32.HS_DIAGCROSS, 0x00000005) + expect_value(t, win32.HS_API_MAX, 0x0000000C) + expect_value(t, win32.PS_SOLID, 0x00000000) + expect_value(t, win32.PS_DASH, 0x00000001) + expect_value(t, win32.PS_DOT, 0x00000002) + expect_value(t, win32.PS_DASHDOT, 0x00000003) + expect_value(t, win32.PS_DASHDOTDOT, 0x00000004) + expect_value(t, win32.PS_NULL, 0x00000005) + expect_value(t, win32.PS_INSIDEFRAME, 0x00000006) + expect_value(t, win32.PS_USERSTYLE, 0x00000007) + expect_value(t, win32.PS_ALTERNATE, 0x00000008) + expect_value(t, win32.PS_STYLE_MASK, 0x0000000F) + expect_value(t, win32.PS_ENDCAP_ROUND, 0x00000000) + expect_value(t, win32.PS_ENDCAP_SQUARE, 0x00000100) + expect_value(t, win32.PS_ENDCAP_FLAT, 0x00000200) + expect_value(t, win32.PS_ENDCAP_MASK, 0x00000F00) + expect_value(t, win32.PS_JOIN_ROUND, 0x00000000) + expect_value(t, win32.PS_JOIN_BEVEL, 0x00001000) + expect_value(t, win32.PS_JOIN_MITER, 0x00002000) + expect_value(t, win32.PS_COSMETIC, 0x00000000) + expect_value(t, win32.PS_GEOMETRIC, 0x00010000) + expect_value(t, win32.PS_TYPE_MASK, 0x000F0000) + // Binary raster ops + expect_value(t, win32.R2_BLACK, 0x00000001) + expect_value(t, win32.R2_NOTMERGEPEN, 0x00000002) + expect_value(t, win32.R2_MASKNOTPEN, 0x00000003) + expect_value(t, win32.R2_NOTCOPYPEN, 0x00000004) + expect_value(t, win32.R2_MASKPENNOT, 0x00000005) + expect_value(t, win32.R2_NOT, 0x00000006) + expect_value(t, win32.R2_XORPEN, 0x00000007) + expect_value(t, win32.R2_NOTMASKPEN, 0x00000008) + expect_value(t, win32.R2_MASKPEN, 0x00000009) + expect_value(t, win32.R2_NOTXORPEN, 0x0000000A) + expect_value(t, win32.R2_NOP, 0x0000000B) + expect_value(t, win32.R2_MERGENOTPEN, 0x0000000C) + expect_value(t, win32.R2_COPYPEN, 0x0000000D) + expect_value(t, win32.R2_MERGEPENNOT, 0x0000000E) + expect_value(t, win32.R2_MERGEPEN, 0x0000000F) + expect_value(t, win32.R2_WHITE, 0x00000010) + // Ternary raster operations + expect_value(t, win32.SRCCOPY, 0x00CC0020) + expect_value(t, win32.SRCPAINT, 0x00EE0086) + expect_value(t, win32.SRCAND, 0x008800C6) + expect_value(t, win32.SRCINVERT, 0x00660046) + expect_value(t, win32.SRCERASE, 0x00440328) + expect_value(t, win32.NOTSRCCOPY, 0x00330008) + expect_value(t, win32.NOTSRCERASE, 0x001100A6) + expect_value(t, win32.MERGECOPY, 0x00C000CA) + expect_value(t, win32.MERGEPAINT, 0x00BB0226) + expect_value(t, win32.PATCOPY, 0x00F00021) + expect_value(t, win32.PATPAINT, 0x00FB0A09) + expect_value(t, win32.PATINVERT, 0x005A0049) + expect_value(t, win32.DSTINVERT, 0x00550009) + expect_value(t, win32.BLACKNESS, 0x00000042) + expect_value(t, win32.WHITENESS, 0x00FF0062) + expect_value(t, win32.NOMIRRORBITMAP, 0x80000000) + expect_value(t, win32.CAPTUREBLT, 0x40000000) + // Region Flags + expect_value(t, win32.ERROR, 0x00000000) + expect_value(t, win32.NULLREGION, 0x00000001) + expect_value(t, win32.SIMPLEREGION, 0x00000002) + expect_value(t, win32.COMPLEXREGION, 0x00000003) + expect_value(t, win32.RGN_ERROR, 0x00000000) + // CombineRgn() Styles + expect_value(t, win32.RGN_AND, 0x00000001) + expect_value(t, win32.RGN_OR, 0x00000002) + expect_value(t, win32.RGN_XOR, 0x00000003) + expect_value(t, win32.RGN_DIFF, 0x00000004) + expect_value(t, win32.RGN_COPY, 0x00000005) + // StretchBlt() Modes + expect_value(t, win32.BLACKONWHITE, 0x00000001) + expect_value(t, win32.WHITEONBLACK, 0x00000002) + expect_value(t, win32.COLORONCOLOR, 0x00000003) + expect_value(t, win32.HALFTONE, 0x00000004) + // PolyFill() Modes + expect_value(t, win32.ALTERNATE, 0x00000001) + expect_value(t, win32.WINDING, 0x00000002) + // Layout Orientation Options + expect_value(t, win32.LAYOUT_RTL, 0x00000001) + expect_value(t, win32.LAYOUT_BTT, 0x00000002) + expect_value(t, win32.LAYOUT_VBH, 0x00000004) + expect_value(t, win32.LAYOUT_ORIENTATIONMASK, 0x00000007) + // Text Alignment Options + expect_value(t, win32.TA_NOUPDATECP, 0x00000000) + expect_value(t, win32.TA_UPDATECP, 0x00000001) + expect_value(t, win32.TA_LEFT, 0x00000000) + expect_value(t, win32.TA_RIGHT, 0x00000002) + expect_value(t, win32.TA_CENTER, 0x00000006) + expect_value(t, win32.TA_TOP, 0x00000000) + expect_value(t, win32.TA_BOTTOM, 0x00000008) + expect_value(t, win32.TA_BASELINE, 0x00000018) + expect_value(t, win32.TA_RTLREADING, 0x00000100) + expect_value(t, win32.TA_MASK, 0x0000011F) +} + +@(test) +verify_winmm :: proc(t: ^testing.T) { + // timeapi.h + expect_size(t, win32.TIMECAPS, 8) + // mmsyscom.h + expect_size(t, win32.MMVERSION, 4) + expect_size(t, win32.MMTIME, 12) + // mmeapi.h + expect_size(t, win32.WAVEFORMATEX, 20) + expect_size(t, win32.WAVEHDR, 48) + expect_size(t, win32.WAVEINCAPSW, 80) + expect_size(t, win32.WAVEOUTCAPSW, 84) +} + +@(test) +verify_advapi32 :: proc(t: ^testing.T) { + // wincrypt.h + expect_size(t, win32.HCRYPTPROV, 8) +} + +@(test) +verify_winnls :: proc(t: ^testing.T) { + // winnls.h + expect_value(t, win32.CP_ACP, 0x00000000) + expect_value(t, win32.CP_OEMCP, 0x00000001) + expect_value(t, win32.CP_MACCP, 0x00000002) + expect_value(t, win32.CP_THREAD_ACP, 0x00000003) + expect_value(t, win32.CP_SYMBOL, 0x0000002A) + expect_value(t, win32.CP_UTF7, 0x0000FDE8) + expect_value(t, win32.CP_UTF8, 0x0000FDE9) + expect_value(t, win32.MAX_DEFAULTCHAR, 0x00000002) + expect_value(t, win32.MAX_LEADBYTES, 0x0000000C) + expect_value(t, win32.LOCALE_NAME_MAX_LENGTH, 0x00000055) + expect_value(t, win32.LOCALE_NAME_USER_DEFAULT, 0x00000000) + expect_value_str(t, win32.LOCALE_NAME_INVARIANT, L("")) + expect_value_str(t, win32.LOCALE_NAME_SYSTEM_DEFAULT, L("!x-sys-default-locale")) + expect_size(t, win32.LCTYPE, 4) + expect_size(t, win32.CPINFOEXW, 544) +} + +@(test) +verify_winreg :: proc(t: ^testing.T) { + // winreg.h + expect_value(t, win32.RRF_RT_REG_NONE, 0x00000001) + expect_value(t, win32.RRF_RT_REG_SZ, 0x00000002) + expect_value(t, win32.RRF_RT_REG_EXPAND_SZ, 0x00000004) + expect_value(t, win32.RRF_RT_REG_BINARY, 0x00000008) + expect_value(t, win32.RRF_RT_REG_DWORD, 0x00000010) + expect_value(t, win32.RRF_RT_REG_MULTI_SZ, 0x00000020) + expect_value(t, win32.RRF_RT_REG_QWORD, 0x00000040) + expect_value(t, win32.RRF_RT_DWORD, 0x00000018) + expect_value(t, win32.RRF_RT_QWORD, 0x00000048) + expect_value(t, win32.RRF_RT_ANY, 0x0000FFFF) + expect_value(t, win32.RRF_NOEXPAND, 0x10000000) + expect_value(t, win32.RRF_ZEROONFAILURE, 0x20000000) + // winnt.h + expect_value(t, u32(win32.HKEY_CLASSES_ROOT), 0x80000000) + expect_value(t, u32(win32.HKEY_CURRENT_USER), 0x80000001) + expect_value(t, u32(win32.HKEY_LOCAL_MACHINE), 0x80000002) + expect_value(t, u32(win32.HKEY_USERS), 0x80000003) + expect_value(t, u32(win32.HKEY_PERFORMANCE_DATA), 0x80000004) + expect_value(t, u32(win32.HKEY_PERFORMANCE_TEXT), 0x80000050) + expect_value(t, u32(win32.HKEY_PERFORMANCE_NLSTEXT), 0x80000060) + expect_value(t, u32(win32.HKEY_CURRENT_CONFIG), 0x80000005) + expect_value(t, u32(win32.HKEY_DYN_DATA), 0x80000006) + expect_value(t, u32(win32.HKEY_CURRENT_USER_LOCAL_SETTINGS), 0x80000007) + expect_value(t, win32.DELETE, 0x00010000) + expect_value(t, win32.READ_CONTROL, 0x00020000) + expect_value(t, win32.WRITE_DAC, 0x00040000) + expect_value(t, win32.WRITE_OWNER, 0x00080000) + expect_value(t, win32.SYNCHRONIZE, 0x00100000) + expect_value(t, win32.KEY_QUERY_VALUE, 0x00000001) + expect_value(t, win32.KEY_SET_VALUE, 0x00000002) + expect_value(t, win32.KEY_CREATE_SUB_KEY, 0x00000004) + expect_value(t, win32.KEY_ENUMERATE_SUB_KEYS, 0x00000008) + expect_value(t, win32.KEY_NOTIFY, 0x00000010) + expect_value(t, win32.KEY_CREATE_LINK, 0x00000020) + expect_value(t, win32.KEY_WOW64_32KEY, 0x00000200) + expect_value(t, win32.KEY_WOW64_64KEY, 0x00000100) + expect_value(t, win32.KEY_WOW64_RES, 0x00000300) + expect_value(t, win32.KEY_READ, 0x00020019) + expect_value(t, win32.KEY_WRITE, 0x00020006) + expect_value(t, win32.KEY_EXECUTE, 0x00020019) + expect_value(t, win32.KEY_ALL_ACCESS, 0x000F003F) +} + +@(test) +verify_verrsrc :: proc(t: ^testing.T) { + // verrsrc.h + expect_value(t, win32.VS_VERSION_INFO, 0x00000001) + expect_value(t, win32.VS_USER_DEFINED, 0x00000064) + expect_size(t, win32.VS_FIXEDFILEINFO, 52) + expect_value(t, win32.VS_FFI_SIGNATURE, 0xFEEF04BD) +} + +@(test) +verify_error_codes :: proc(t: ^testing.T) { + // winerror.h + expect_value(t, win32.ERROR_SUCCESS, 0x00000000) + expect_value(t, win32.NO_ERROR, 0x00000000) + expect_value(t, win32.SEC_E_OK, 0x00000000) + + expect_value(t, win32.ERROR_INVALID_FUNCTION, 0x00000001) + expect_value(t, win32.ERROR_FILE_NOT_FOUND, 0x00000002) + expect_value(t, win32.ERROR_PATH_NOT_FOUND, 0x00000003) + expect_value(t, win32.ERROR_ACCESS_DENIED, 0x00000005) + expect_value(t, win32.ERROR_INVALID_HANDLE, 0x00000006) + expect_value(t, win32.ERROR_NOT_ENOUGH_MEMORY, 0x00000008) + expect_value(t, win32.ERROR_INVALID_BLOCK, 0x00000009) + expect_value(t, win32.ERROR_BAD_ENVIRONMENT, 0x0000000A) + expect_value(t, win32.ERROR_BAD_FORMAT, 0x0000000B) + expect_value(t, win32.ERROR_INVALID_ACCESS, 0x0000000C) + expect_value(t, win32.ERROR_INVALID_DATA, 0x0000000D) + expect_value(t, win32.ERROR_OUTOFMEMORY, 0x0000000E) + expect_value(t, win32.ERROR_INVALID_DRIVE, 0x0000000F) + expect_value(t, win32.ERROR_CURRENT_DIRECTORY, 0x00000010) + expect_value(t, win32.ERROR_NO_MORE_FILES, 0x00000012) + expect_value(t, win32.ERROR_SHARING_VIOLATION, 0x00000020) + expect_value(t, win32.ERROR_LOCK_VIOLATION, 0x00000021) + expect_value(t, win32.ERROR_HANDLE_EOF, 0x00000026) + expect_value(t, win32.ERROR_NOT_SUPPORTED, 0x00000032) + expect_value(t, win32.ERROR_FILE_EXISTS, 0x00000050) + expect_value(t, win32.ERROR_INVALID_PARAMETER, 0x00000057) + expect_value(t, win32.ERROR_BROKEN_PIPE, 0x0000006D) + expect_value(t, win32.ERROR_CALL_NOT_IMPLEMENTED, 0x00000078) + expect_value(t, win32.ERROR_INSUFFICIENT_BUFFER, 0x0000007A) + expect_value(t, win32.ERROR_INVALID_NAME, 0x0000007B) + expect_value(t, win32.ERROR_BAD_ARGUMENTS, 0x000000A0) + expect_value(t, win32.ERROR_LOCK_FAILED, 0x000000A7) + expect_value(t, win32.ERROR_ALREADY_EXISTS, 0x000000B7) + expect_value(t, win32.ERROR_NO_DATA, 0x000000E8) + expect_value(t, win32.ERROR_ENVVAR_NOT_FOUND, 0x000000CB) + expect_value(t, win32.ERROR_OPERATION_ABORTED, 0x000003E3) + expect_value(t, win32.ERROR_IO_PENDING, 0x000003E5) + expect_value(t, win32.ERROR_NO_UNICODE_TRANSLATION, 0x00000459) + expect_value(t, win32.ERROR_TIMEOUT, 0x000005B4) + expect_value(t, win32.ERROR_DATATYPE_MISMATCH, 0x0000065D) + expect_value(t, win32.ERROR_UNSUPPORTED_TYPE, 0x0000065E) + expect_value(t, win32.ERROR_NOT_SAME_OBJECT, 0x00000678) + expect_value(t, win32.ERROR_PIPE_CONNECTED, 0x00000217) + expect_value(t, win32.ERROR_PIPE_BUSY, 0x000000E7) + + expect_value(t, win32.S_OK, 0x00000000) + expect_value(t, win32.E_NOTIMPL, 0x80004001) + expect_value(t, win32.E_NOINTERFACE, 0x80004002) + expect_value(t, win32.E_POINTER, 0x80004003) + expect_value(t, win32.E_ABORT, 0x80004004) + expect_value(t, win32.E_FAIL, 0x80004005) + expect_value(t, win32.E_UNEXPECTED, 0x8000FFFF) + expect_value(t, win32.E_ACCESSDENIED, 0x80070005) + expect_value(t, win32.E_HANDLE, 0x80070006) + expect_value(t, win32.E_OUTOFMEMORY, 0x8007000E) + expect_value(t, win32.E_INVALIDARG, 0x80070057) +} + +@(test) +verify_error_helpers :: proc(t: ^testing.T) { + // winerror.h + expect_value(t, win32.SUCCEEDED(-1), 0x00000000) + expect_value(t, win32.SUCCEEDED(0), 0x00000001) + expect_value(t, win32.SUCCEEDED(1), 0x00000001) + + expect_value(t, win32.FAILED(-1), 0x00000001) + expect_value(t, win32.FAILED(0), 0x00000000) + expect_value(t, win32.FAILED(1), 0x00000000) + + expect_value(t, win32.IS_ERROR(-1), 0x00000001) + expect_value(t, win32.IS_ERROR(0), 0x00000000) + expect_value(t, win32.IS_ERROR(1), 0x00000000) + + expect_value(t, win32.HRESULT_CODE(0xFFFFCCCC), 0x0000CCCC) + expect_value(t, win32.HRESULT_FACILITY(0xFFFFCCCC), 0x00001FFF) + expect_value(t, win32.HRESULT_SEVERITY(0x12345678), 0x00000000) + expect_value(t, win32.HRESULT_SEVERITY(0x87654321), 0x00000001) + + expect_value(t, win32.MAKE_HRESULT(1, 2, 3), 0x80020003) +} diff --git a/tests/core/sys/windows/test_winerror.odin b/tests/core/sys/windows/test_winerror.odin new file mode 100644 index 000000000..adbdb7ce1 --- /dev/null +++ b/tests/core/sys/windows/test_winerror.odin @@ -0,0 +1,34 @@ +//+build windows +package test_core_sys_windows + +import "core:testing" +import win32 "core:sys/windows" + +@(test) +make_hresult :: proc(t: ^testing.T) { + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.SUCCESS, win32.FACILITY.NULL, win32.ERROR_SUCCESS), win32.S_OK) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.NULL, 0x4001), win32.E_NOTIMPL) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.NULL, 0x4002), win32.E_NOINTERFACE) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.NULL, 0x4003), win32.E_POINTER) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.NULL, 0x4004), win32.E_ABORT) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.NULL, 0x4005), win32.E_FAIL) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.NULL, 0xFFFF), win32.E_UNEXPECTED) + + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.ERROR_ACCESS_DENIED), win32.E_ACCESSDENIED) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.ERROR_INVALID_HANDLE), win32.E_HANDLE) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.ERROR_OUTOFMEMORY), win32.E_OUTOFMEMORY) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.ERROR_INVALID_PARAMETER), win32.E_INVALIDARG) + + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.System_Error.ACCESS_DENIED), win32.E_ACCESSDENIED) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.System_Error.INVALID_HANDLE), win32.E_HANDLE) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.System_Error.OUTOFMEMORY), win32.E_OUTOFMEMORY) + expect_value(t, win32.MAKE_HRESULT(win32.SEVERITY.ERROR, win32.FACILITY.WIN32, win32.System_Error.INVALID_PARAMETER), win32.E_INVALIDARG) +} + +@(test) +decode_hresult :: proc(t: ^testing.T) { + s, f, c := win32.DECODE_HRESULT(win32.E_INVALIDARG) + expect_value(t, s, win32.SEVERITY.ERROR) + expect_value(t, f, win32.FACILITY.WIN32) + expect_value(t, c, win32.System_Error.INVALID_PARAMETER) +} diff --git a/tests/core/sys/windows/win32gen/build.bat b/tests/core/sys/windows/win32gen/build.bat new file mode 100644 index 000000000..83b2da4a2 --- /dev/null +++ b/tests/core/sys/windows/win32gen/build.bat @@ -0,0 +1,70 @@ +@echo off +if "%VSCMD_ARG_TGT_ARCH%"=="" call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 +set genconfig=Release +pushd %~dp0 + +set genname=win32gen +set cl_output_path=%genname%\x64\%genconfig% +set gen_output_path=x64\%genconfig% +set genexe=%gen_output_path%\%genname%.exe + +if exist "%genexe%" (del /f "%genexe%" > NUL 2> NUL) + +:compiling +echo Compiling... +mkdir %cl_output_path% > NUL 2> NUL +set compiler_flags=^ +/c /Zi /nologo /W3 /WX- ^ +/diagnostics:column /sdl /O2 /Oi /GL ^ +/D NDEBUG /D _CONSOLE /D _UNICODE /D UNICODE ^ +/Gm- /EHsc /MD /GS /Gy /fp:precise ^ +/Zc:wchar_t /Zc:forScope /Zc:inline ^ +/std:c++20 /permissive- /Fo"%cl_output_path%\\" ^ +/external:W3 /Gd /TP /FC /errorReport:queue + +@echo on +cl %compiler_flags% win32gen.cpp +@echo off +if %ERRORLEVEL% NEQ 0 (goto error) + +:linking +echo Linking... +mkdir "%gen_output_path%" > NUL 2> NUL +set libs=^ +kernel32.lib ^ +user32.lib ^ +gdi32.lib ^ +comdlg32.lib ^ +advapi32.lib ^ +shell32.lib ^ +ole32.lib ^ +uuid.lib +set linker_flags=^ +/ERRORREPORT:QUEUE ^ +/OUT:%genexe% ^ +/NOLOGO ^ +%libs% ^ +/MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed ^ +/SUBSYSTEM:CONSOLE ^ +/OPT:REF /OPT:ICF /LTCG:incremental ^ +/LTCGOUT:"%cl_output_path%\%genname%.iobj" ^ +/TLBID:1 /DYNAMICBASE /NXCOMPAT ^ +/IMPLIB:"%gen_output_path%\%genname%.lib" ^ +/MACHINE:X64 + +@echo on +link %linker_flags% %cl_output_path%\%genname%.obj +@echo off +if %ERRORLEVEL% NEQ 0 (goto error) + +:generate +echo Generating... +%genexe% ..\test_windows_generated.odin +goto done + +:error +echo Last command returned %ERRORLEVEL% + +:done +popd +echo Done. diff --git a/tests/core/sys/windows/win32gen/win32gen.cpp b/tests/core/sys/windows/win32gen/win32gen.cpp new file mode 100644 index 000000000..731173aa6 --- /dev/null +++ b/tests/core/sys/windows/win32gen/win32gen.cpp @@ -0,0 +1,932 @@ +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +using namespace std; +using namespace std::filesystem; + +static std::string ConvertLPCWSTRToString(const LPCWSTR lpcwszStr) +{ + int strLength = WideCharToMultiByte(CP_UTF8, 0, lpcwszStr, -1, nullptr, 0, nullptr, nullptr) - 1; + string str(strLength, 0); + WideCharToMultiByte(CP_UTF8, 0, lpcwszStr, -1, &str[0], strLength, nullptr, nullptr); + return std::string(str); +} + +#define test_proc_begin() out \ + << endl \ + << "@(test)" << endl \ + << __func__ << " :: proc(t: ^testing.T) {" << endl + +#define test_proc_end() out \ + << "}" << endl + +#define test_proc_using(name) out \ + << '\t' << "using " << name << endl + +#define test_proc_comment(comment) out \ + << '\t' << "// " << comment << endl + +#define expect_size(s) out \ + << '\t' << "expect_size(t, win32." << #s << ", " \ + << std::dec << sizeof(s) << ")" << endl + +#define expect_value(s) out \ + << '\t' << "expect_value(t, win32." << #s << ", " \ + << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << s << ")" << endl + +#define expect_value_32(s) out \ + << '\t' << "expect_value(t, u32(win32." << #s << "), " \ + << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << (ULONG)(ULONG_PTR)(s) << ")" << endl + +#define expect_value_64(s) out \ + << '\t' << "expect_value_64(t, u64(win32." << #s << "), " \ + << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << (ULONGLONG)(ULONG_PTR)(s) << ")" << endl + +#define expect_value_uintptr(s) out \ + << '\t' << "expect_value_64(t, uintptr(win32." << #s << "), " \ + << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << (ULONG_PTR)(s) << ")" << endl + +#define expect_value_str(s) out \ + << '\t' << "expect_value_str(t, win32." << #s << ", L(\"" << ConvertLPCWSTRToString(s) << "\"))" << endl + +static void verify_win32_type_sizes(ofstream& out) { + test_proc_begin(); + test_proc_comment("minwindef.h"); + expect_size(ULONG); // unsigned long + expect_size(PULONG); // unsigned long* + expect_size(USHORT); // unsigned short + expect_size(PUSHORT); // unsigned short* + expect_size(UCHAR); // unsigned char + // expect_size(PUCHAR); + // expect_size(PSZ); + expect_size(DWORD); // unsigned long + expect_size(BOOL); // int + expect_size(BYTE); // unsigned char + expect_size(WORD); // unsigned short +#ifdef PROPVARIANT + expect_size(FLOAT); // float + expect_size(DOUBLE); // double + expect_size(DATE); // double +#endif + // expect_size(PFLOAT); + expect_size(PBOOL); + expect_size(LPBOOL); + expect_size(PBYTE); + expect_size(LPBYTE); + expect_size(PINT); + expect_size(LPINT); + // expect_size(PWORD); + expect_size(LPWORD); + // expect_size(LPLONG); + expect_size(PDWORD); + expect_size(LPDWORD); + expect_size(LPVOID); + expect_size(LPCVOID); + + expect_size(INT); // int + expect_size(UINT); // unsigned int + expect_size(PUINT); // unsigned int* + + expect_size(UINT_PTR); // unsigned __int64 + expect_size(LONG_PTR); // __int64 + + expect_size(HANDLE); // void * + expect_size(WPARAM); // unsigned __int64 + expect_size(LPARAM); // __int64 + expect_size(LRESULT); // __int64 + + expect_size(LPHANDLE); + expect_size(HGLOBAL); // void * + // expect_size(HLOCAL); + // expect_size(GLOBALHANDLE); + // expect_size(LOCALHANDLE); + + expect_size(ATOM); // unsigned short + expect_size(HKEY); + expect_size(PHKEY); + // expect_size(HMETAFILE); + expect_size(HINSTANCE); + expect_size(HMODULE); + expect_size(HRGN); + expect_size(HRSRC); + // expect_size(HSPRITE); + // expect_size(HLSURF); + // expect_size(HSTR); + // expect_size(HTASK); + // expect_size(HWINSTA); + // expect_size(HKL); + + //expect_size(HFILE); + + test_proc_comment("windef.h"); + expect_size(HWND); + expect_size(HHOOK); + expect_size(HGDIOBJ); + expect_size(HBITMAP); + expect_size(HPALETTE); + expect_size(HBRUSH); + expect_size(HPEN); + expect_size(HFONT); + expect_size(HICON); + expect_size(HMENU); + expect_size(HCURSOR); + expect_size(COLORREF); + expect_size(RECT); + expect_size(POINT); + expect_size(SIZE); + + test_proc_comment("wtypes.h"); + expect_size(DECIMAL); +#ifdef PROPVARIANT + expect_size(CY); +#endif + + test_proc_comment("fileapi.h"); + expect_size(WIN32_FILE_ATTRIBUTE_DATA); + + test_proc_comment("libloaderapi.h"); + expect_size(ENUMRESNAMEPROCW); + expect_size(ENUMRESTYPEPROCW); + + test_proc_comment("minwinbase.h"); + expect_size(SYSTEMTIME); + expect_size(WIN32_FIND_DATAW); + expect_size(CRITICAL_SECTION); + // expect_size(PROCESS_HEAP_ENTRY); + expect_size(REASON_CONTEXT); + + test_proc_comment("guiddef.h"); + expect_size(GUID); + expect_size(IID); + expect_size(CLSID); + test_proc_comment("combaseapi.h"); + expect_size(SCODE); +#ifdef PROPVARIANT + expect_size(VARTYPE); + expect_size(VARIANT_BOOL); + expect_size(CLIPDATA); + expect_size(SAFEARRAYBOUND); + expect_size(SAFEARRAY); + expect_size(CAPROPVARIANT); + expect_size(PROPVARIANT); +#endif + test_proc_comment("commdlg.h"); + expect_size(OPENFILENAMEW); + // test_proc_comment("windns.h"); + // expect_size(DNS_RECORDA); + // expect_size(DNS_RECORDW); + // SHCreateLibrary + test_proc_comment("wtypesbase.h"); + expect_size(OLECHAR); + //test_proc_comment("objbase.h"); + //expect_value(COINIT_MULTITHREADED); + //expect_value(COINIT_APARTMENTTHREADED); + //expect_value(COINIT_DISABLE_OLE1DDE); + //expect_value(COINIT_SPEED_OVER_MEMORY); + test_proc_end(); +} + +static void verify_macros(ofstream& out) { + test_proc_begin(); + test_proc_comment("minwindef.h"); + expect_value(MAKEWORD(1, 2)); + expect_value(MAKEWORD(0x1111, 0x2222)); + expect_value(MAKELONG(1, 2)); + expect_value(MAKELONG(0x1111, 0x2222)); + expect_value(LOWORD(0x12345678)); + expect_value(HIWORD(0x12345678)); + expect_value_32(LOBYTE(0x1234)); + expect_value_32(HIBYTE(0x1234)); + test_proc_comment("winuser.h"); + expect_value(MAKEWPARAM(1, 2)); + expect_value(MAKEWPARAM(0x1111, 0x2222)); + expect_value(MAKELPARAM(1, 2)); + expect_value(MAKELPARAM(0x1111, 0x2222)); + expect_value(MAKELRESULT(1, 2)); + expect_value(MAKELRESULT(0x1111, 0x2222)); + test_proc_comment("winnt.h"); + expect_value(MAKELCID(1, 2)); + expect_value(MAKELCID(0x1111, 0x2222)); + expect_value(MAKELANGID(1, 2)); + expect_value(MAKELANGID(0x111, 0x222)); + expect_value(LANGIDFROMLCID(0x12345678)); + test_proc_end(); +} + +static void verify_winnt(ofstream& out) { + test_proc_begin(); + test_proc_comment("winnt.h"); + expect_size(CHAR); + expect_size(SHORT); + expect_size(LONG); + expect_size(INT); + expect_size(WCHAR); + // expect_size(LONGLONG); + expect_size(ULONGLONG); + expect_size(LARGE_INTEGER); + expect_size(PLARGE_INTEGER); + expect_size(ULARGE_INTEGER); + expect_size(PULARGE_INTEGER); + expect_size(BOOLEAN); + expect_size(HANDLE); + expect_size(PHANDLE); + expect_size(HRESULT); + // expect_size(CCHAR); + expect_size(LCID); + expect_size(LANGID); + + expect_size(LUID); + expect_size(SECURITY_INFORMATION); + expect_size(ACCESS_MASK); + expect_size(REGSAM); + expect_value(LANG_NEUTRAL); + expect_value(LANG_INVARIANT); + expect_value(SUBLANG_NEUTRAL); + expect_value(SUBLANG_DEFAULT); + test_proc_end(); +} + +static void verify_winuser(ofstream& out) { + test_proc_begin(); + test_proc_comment("winuser.h"); + //expect_value(UOI_FLAGS); + expect_size(USEROBJECTFLAGS); + expect_size(MSG); + expect_size(WINDOWPOS); + expect_size(ACCEL); + expect_size(MENUITEMINFOW); + expect_size(PAINTSTRUCT); + expect_size(CREATESTRUCTW); + expect_size(WINDOWPLACEMENT); + expect_size(MOUSEINPUT); + expect_size(KEYBDINPUT); + expect_size(HARDWAREINPUT); + expect_size(INPUT); + + // expect_size(ICONINFO); + // expect_size(CURSORSHAPE); + expect_size(ICONINFOEXW); + + expect_size(CURSORINFO); + //expect_value(CURSOR_SHOWING); + //expect_value(CURSOR_SUPPRESSED); + + expect_size(WINDOWINFO); + + expect_size(RAWINPUTHEADER); + //expect_size(RAWHID); + expect_size(RAWMOUSE); + expect_size(RAWKEYBOARD); + expect_size(RAWINPUT); + expect_size(RAWINPUTDEVICE); + expect_size(RAWINPUTDEVICELIST); + + expect_size(RID_DEVICE_INFO_HID); + expect_size(RID_DEVICE_INFO_KEYBOARD); + expect_size(RID_DEVICE_INFO_MOUSE); + expect_size(RID_DEVICE_INFO); + expect_value(GET_RAWINPUT_CODE_WPARAM(0x12345678)); + + expect_size(DRAWTEXTPARAMS); + expect_size(BSMINFO); + + expect_value(BROADCAST_QUERY_DENY); + expect_value_64(HWND_BROADCAST); + expect_value_64(HWND_MESSAGE); + + expect_value_uintptr(MAKEINTRESOURCEW(1)); + expect_value_uintptr(MAKEINTRESOURCEW(0x12345678)); + + expect_value_uintptr(RT_CURSOR); + expect_value_uintptr(RT_BITMAP); + expect_value_uintptr(RT_ICON); + expect_value_uintptr(RT_MENU); + expect_value_uintptr(RT_DIALOG); + expect_value_uintptr(RT_STRING); + expect_value_uintptr(RT_FONTDIR); + expect_value_uintptr(RT_FONT); + expect_value_uintptr(RT_ACCELERATOR); + expect_value_uintptr(RT_RCDATA); + expect_value_uintptr(RT_MESSAGETABLE); + expect_value_uintptr(RT_GROUP_CURSOR); + expect_value_uintptr(RT_GROUP_ICON); + expect_value_uintptr(RT_VERSION); + expect_value_uintptr(RT_DLGINCLUDE); + expect_value_uintptr(RT_PLUGPLAY); + expect_value_uintptr(RT_VXD); + expect_value_uintptr(RT_ANICURSOR); + expect_value_uintptr(RT_ANIICON); + expect_value_uintptr(RT_MANIFEST); + + expect_value_uintptr(CREATEPROCESS_MANIFEST_RESOURCE_ID); + expect_value_uintptr(ISOLATIONAWARE_MANIFEST_RESOURCE_ID); + expect_value_uintptr(ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID); + expect_value_uintptr(ISOLATIONPOLICY_MANIFEST_RESOURCE_ID); + expect_value_uintptr(ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID); + expect_value_uintptr(MINIMUM_RESERVED_MANIFEST_RESOURCE_ID); + expect_value_uintptr(MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID); + + expect_value(SM_CXICON); + expect_value(SM_CYICON); + + expect_value(LR_DEFAULTCOLOR); + expect_value(LR_MONOCHROME); + expect_value(LR_COLOR); + expect_value(LR_COPYRETURNORG); + expect_value(LR_COPYDELETEORG); + expect_value(LR_LOADFROMFILE); + expect_value(LR_LOADTRANSPARENT); + expect_value(LR_DEFAULTSIZE); + expect_value(LR_VGACOLOR); + expect_value(LR_LOADMAP3DCOLORS); + expect_value(LR_CREATEDIBSECTION); + expect_value(LR_COPYFROMRESOURCE); + expect_value(LR_SHARED); + + expect_value(NIM_ADD); + expect_value(NIM_MODIFY); + expect_value(NIM_DELETE); + expect_value(NIM_SETFOCUS); + expect_value(NIM_SETVERSION); + + expect_value(NIF_MESSAGE); + expect_value(NIF_ICON); + expect_value(NIF_TIP); + expect_value(NIF_STATE); + expect_value(NIF_INFO); + expect_value(NIF_GUID); + expect_value(NIF_REALTIME); + expect_value(NIF_SHOWTIP); + + expect_value(MF_INSERT); + expect_value(MF_CHANGE); + expect_value(MF_APPEND); + expect_value(MF_DELETE); + expect_value(MF_REMOVE); + expect_value(MF_BYCOMMAND); + expect_value(MF_BYPOSITION); + expect_value(MF_SEPARATOR); + expect_value(MF_ENABLED); + expect_value(MF_GRAYED); + expect_value(MF_DISABLED); + expect_value(MF_UNCHECKED); + expect_value(MF_CHECKED); + expect_value(MF_USECHECKBITMAPS); + expect_value(MF_STRING); + expect_value(MF_BITMAP); + expect_value(MF_OWNERDRAW); + expect_value(MF_POPUP); + expect_value(MF_MENUBARBREAK); + expect_value(MF_MENUBREAK); + expect_value(MF_UNHILITE); + expect_value(MF_HILITE); + expect_value(MF_DEFAULT); + expect_value(MF_SYSMENU); + expect_value(MF_HELP); + expect_value(MF_RIGHTJUSTIFY); + expect_value(MF_MOUSESELECT); + expect_value(MF_END); + + expect_value(MFS_GRAYED); + expect_value(MFS_DISABLED); + expect_value(MFS_CHECKED); + expect_value(MFS_HILITE); + expect_value(MFS_ENABLED); + expect_value(MFS_UNCHECKED); + expect_value(MFS_UNHILITE); + expect_value(MFS_DEFAULT); + + expect_value(TPM_LEFTBUTTON); + expect_value(TPM_RIGHTBUTTON); + expect_value(TPM_LEFTALIGN); + expect_value(TPM_CENTERALIGN); + expect_value(TPM_RIGHTALIGN); + expect_value(TPM_TOPALIGN); + expect_value(TPM_VCENTERALIGN); + expect_value(TPM_BOTTOMALIGN); + + expect_value(TPM_HORIZONTAL); + expect_value(TPM_VERTICAL); + expect_value(TPM_NONOTIFY); + expect_value(TPM_RETURNCMD); + expect_value(TPM_RECURSE); + expect_value(TPM_HORPOSANIMATION); + expect_value(TPM_HORNEGANIMATION); + expect_value(TPM_VERPOSANIMATION); + expect_value(TPM_VERNEGANIMATION); + expect_value(TPM_NOANIMATION); + expect_value(TPM_LAYOUTRTL); + expect_value(TPM_WORKAREA); + + expect_value(MIIM_STATE); + expect_value(MIIM_ID); + expect_value(MIIM_SUBMENU); + expect_value(MIIM_CHECKMARKS); + expect_value(MIIM_TYPE); + expect_value(MIIM_DATA); + expect_value(MIIM_STRING); + expect_value(MIIM_BITMAP); + expect_value(MIIM_FTYPE); + + expect_value(ANSI_CHARSET); + expect_value(DEFAULT_CHARSET); + expect_value(SYMBOL_CHARSET); + expect_value(SHIFTJIS_CHARSET); + expect_value(HANGEUL_CHARSET); + expect_value(HANGUL_CHARSET); + expect_value(GB2312_CHARSET); + expect_value(CHINESEBIG5_CHARSET); + expect_value(OEM_CHARSET); + expect_value(JOHAB_CHARSET); + expect_value(HEBREW_CHARSET); + expect_value(ARABIC_CHARSET); + expect_value(GREEK_CHARSET); + expect_value(TURKISH_CHARSET); + expect_value(VIETNAMESE_CHARSET); + expect_value(THAI_CHARSET); + expect_value(EASTEUROPE_CHARSET); + expect_value(RUSSIAN_CHARSET); + expect_value(MAC_CHARSET); + expect_value(BALTIC_CHARSET); + + expect_value(FS_LATIN1); + expect_value(FS_LATIN2); + expect_value(FS_CYRILLIC); + expect_value(FS_GREEK); + expect_value(FS_TURKISH); + expect_value(FS_HEBREW); + expect_value(FS_ARABIC); + expect_value(FS_BALTIC); + expect_value(FS_VIETNAMESE); + expect_value(FS_THAI); + expect_value(FS_JISJAPAN); + expect_value(FS_CHINESESIMP); + expect_value(FS_WANSUNG); + expect_value(FS_CHINESETRAD); + expect_value(FS_JOHAB); + expect_value(FS_SYMBOL); + + expect_value(OUT_DEFAULT_PRECIS); + expect_value(OUT_STRING_PRECIS); + expect_value(OUT_CHARACTER_PRECIS); + expect_value(OUT_STROKE_PRECIS); + expect_value(OUT_TT_PRECIS); + expect_value(OUT_DEVICE_PRECIS); + expect_value(OUT_RASTER_PRECIS); + expect_value(OUT_TT_ONLY_PRECIS); + expect_value(OUT_OUTLINE_PRECIS); + expect_value(OUT_SCREEN_OUTLINE_PRECIS); + expect_value(OUT_PS_ONLY_PRECIS); + + expect_value(CLIP_DEFAULT_PRECIS); + expect_value(CLIP_CHARACTER_PRECIS); + expect_value(CLIP_STROKE_PRECIS); + expect_value(CLIP_MASK); + expect_value(CLIP_LH_ANGLES); + expect_value(CLIP_TT_ALWAYS); + expect_value(CLIP_DFA_DISABLE); + expect_value(CLIP_EMBEDDED); + + expect_value(DEFAULT_QUALITY); + expect_value(DRAFT_QUALITY); + expect_value(PROOF_QUALITY); + expect_value(NONANTIALIASED_QUALITY); + expect_value(ANTIALIASED_QUALITY); + expect_value(CLEARTYPE_QUALITY); + expect_value(CLEARTYPE_NATURAL_QUALITY); + + expect_value(DEFAULT_PITCH); + expect_value(FIXED_PITCH); + expect_value(VARIABLE_PITCH); + expect_value(MONO_FONT); + + expect_value(FF_DONTCARE); + expect_value(FF_ROMAN); + expect_value(FF_SWISS); + expect_value(FF_MODERN); + expect_value(FF_SCRIPT); + expect_value(FF_DECORATIVE); + + test_proc_end(); +} + +static void verify_gdi32(ofstream& out) { + test_proc_begin(); + test_proc_comment("wingdi.h"); + expect_size(DEVMODEW); + // expect_size(RGBTRIPLE); + expect_size(RGBQUAD); + expect_size(PIXELFORMATDESCRIPTOR); + expect_size(BITMAPINFOHEADER); + expect_size(BITMAP); + expect_size(BITMAPV5HEADER); + expect_size(CIEXYZTRIPLE); + expect_size(CIEXYZ); + expect_size(FXPT2DOT30); + expect_size(TEXTMETRICW); + expect_size(POINTFLOAT); + expect_size(GLYPHMETRICSFLOAT); + // expect_size(LOGPALETTE); + expect_size(PALETTEENTRY); + expect_size(DESIGNVECTOR); + expect_value(LF_FACESIZE); + expect_value(LF_FULLFACESIZE); + expect_size(LOGFONTW); + expect_size(ENUMLOGFONTW); + expect_size(ENUMLOGFONTEXW); + expect_size(ENUMLOGFONTEXDVW); + expect_size(NEWTEXTMETRICW); + + expect_size(LAYERPLANEDESCRIPTOR); + expect_size(COLOR16); + expect_size(TRIVERTEX); + expect_size(GRADIENT_TRIANGLE); + expect_size(GRADIENT_RECT); + expect_size(BLENDFUNCTION); + expect_size(DISPLAY_DEVICEW); + expect_value(AC_SRC_OVER); + expect_value(AC_SRC_ALPHA); + expect_value(RGB(12, 34, 56)); + expect_value(PALETTERGB(12, 34, 56)); + expect_value(PALETTEINDEX(123)); + expect_value(GRADIENT_FILL_RECT_H); + expect_value(GRADIENT_FILL_RECT_V); + expect_value(GRADIENT_FILL_TRIANGLE); + + expect_value(BS_SOLID); + expect_value(BS_NULL); + expect_value(BS_HOLLOW); + expect_value(BS_HATCHED); + expect_value(BS_PATTERN); + expect_value(BS_INDEXED); + expect_value(BS_DIBPATTERN); + expect_value(BS_DIBPATTERNPT); + expect_value(BS_PATTERN8X8); + expect_value(BS_DIBPATTERN8X8); + expect_value(BS_MONOPATTERN); + + expect_value(HS_HORIZONTAL); + expect_value(HS_VERTICAL); + expect_value(HS_FDIAGONAL); + expect_value(HS_BDIAGONAL); + expect_value(HS_CROSS); + expect_value(HS_DIAGCROSS); + expect_value(HS_API_MAX); + + expect_value(PS_SOLID); + expect_value(PS_DASH); + expect_value(PS_DOT); + expect_value(PS_DASHDOT); + expect_value(PS_DASHDOTDOT); + expect_value(PS_NULL); + expect_value(PS_INSIDEFRAME); + expect_value(PS_USERSTYLE); + expect_value(PS_ALTERNATE); + expect_value(PS_STYLE_MASK); + + expect_value(PS_ENDCAP_ROUND); + expect_value(PS_ENDCAP_SQUARE); + expect_value(PS_ENDCAP_FLAT); + expect_value(PS_ENDCAP_MASK); + + expect_value(PS_JOIN_ROUND); + expect_value(PS_JOIN_BEVEL); + expect_value(PS_JOIN_MITER); + + expect_value(PS_COSMETIC); + expect_value(PS_GEOMETRIC); + expect_value(PS_TYPE_MASK); + + test_proc_comment("Binary raster ops"); + expect_value(R2_BLACK); + expect_value(R2_NOTMERGEPEN); + expect_value(R2_MASKNOTPEN); + expect_value(R2_NOTCOPYPEN); + expect_value(R2_MASKPENNOT); + expect_value(R2_NOT); + expect_value(R2_XORPEN); + expect_value(R2_NOTMASKPEN); + expect_value(R2_MASKPEN); + expect_value(R2_NOTXORPEN); + expect_value(R2_NOP); + expect_value(R2_MERGENOTPEN); + expect_value(R2_COPYPEN); + expect_value(R2_MERGEPENNOT); + expect_value(R2_MERGEPEN); + expect_value(R2_WHITE); + test_proc_comment("Ternary raster operations"); + expect_value(SRCCOPY); + expect_value(SRCPAINT); + expect_value(SRCAND); + expect_value(SRCINVERT); + expect_value(SRCERASE); + expect_value(NOTSRCCOPY); + expect_value(NOTSRCERASE); + expect_value(MERGECOPY); + expect_value(MERGEPAINT); + expect_value(PATCOPY); + expect_value(PATPAINT); + expect_value(PATINVERT); + expect_value(DSTINVERT); + expect_value(BLACKNESS); + expect_value(WHITENESS); + expect_value(NOMIRRORBITMAP); + expect_value(CAPTUREBLT); + test_proc_comment("Region Flags"); + expect_value(ERROR); + expect_value(NULLREGION); + expect_value(SIMPLEREGION); + expect_value(COMPLEXREGION); + expect_value(RGN_ERROR); + test_proc_comment("CombineRgn() Styles"); + expect_value(RGN_AND); + expect_value(RGN_OR); + expect_value(RGN_XOR); + expect_value(RGN_DIFF); + expect_value(RGN_COPY); + test_proc_comment("StretchBlt() Modes"); + expect_value(BLACKONWHITE); + expect_value(WHITEONBLACK); + expect_value(COLORONCOLOR); + expect_value(HALFTONE); + //expect_value(STRETCH_ANDSCANS); + //expect_value(STRETCH_ORSCANS); + //expect_value(STRETCH_DELETESCANS); + //expect_value(STRETCH_HALFTONE); + test_proc_comment("PolyFill() Modes"); + expect_value(ALTERNATE); + expect_value(WINDING); + test_proc_comment("Layout Orientation Options"); + expect_value(LAYOUT_RTL); + expect_value(LAYOUT_BTT); + expect_value(LAYOUT_VBH); + expect_value(LAYOUT_ORIENTATIONMASK); + test_proc_comment("Text Alignment Options"); + expect_value(TA_NOUPDATECP); + expect_value(TA_UPDATECP); + expect_value(TA_LEFT); + expect_value(TA_RIGHT); + expect_value(TA_CENTER); + expect_value(TA_TOP); + expect_value(TA_BOTTOM); + expect_value(TA_BASELINE); + expect_value(TA_RTLREADING); + expect_value(TA_MASK); + test_proc_end(); +} + +static void verify_winmm(ofstream& out) { + test_proc_begin(); + test_proc_comment("timeapi.h"); + expect_size(TIMECAPS); + test_proc_comment("mmsyscom.h"); + expect_size(MMVERSION); + expect_size(MMTIME); + test_proc_comment("mmeapi.h"); + expect_size(WAVEFORMATEX); + expect_size(WAVEHDR); + expect_size(WAVEINCAPSW); + expect_size(WAVEOUTCAPSW); + test_proc_end(); +} + +static void verify_advapi32(ofstream& out) { + test_proc_begin(); + test_proc_comment("wincrypt.h"); + expect_size(HCRYPTPROV); + test_proc_end(); +} + +static void verify_winnls(ofstream& out) { + test_proc_begin(); + test_proc_comment("winnls.h"); + expect_value(CP_ACP); + expect_value(CP_OEMCP); + expect_value(CP_MACCP); + expect_value(CP_THREAD_ACP); + expect_value(CP_SYMBOL); + expect_value(CP_UTF7); + expect_value(CP_UTF8); + expect_value(MAX_DEFAULTCHAR); + expect_value(MAX_LEADBYTES); + expect_value(LOCALE_NAME_MAX_LENGTH); + expect_value(LOCALE_NAME_USER_DEFAULT); + expect_value_str(LOCALE_NAME_INVARIANT); + expect_value_str(LOCALE_NAME_SYSTEM_DEFAULT); + expect_size(LCTYPE); + expect_size(CPINFOEXW); + test_proc_end(); +} + +static void verify_winreg(ofstream& out) { + test_proc_begin(); + test_proc_comment("winreg.h"); + + expect_value(RRF_RT_REG_NONE); + expect_value(RRF_RT_REG_SZ); + expect_value(RRF_RT_REG_EXPAND_SZ); + expect_value(RRF_RT_REG_BINARY); + expect_value(RRF_RT_REG_DWORD); + expect_value(RRF_RT_REG_MULTI_SZ); + expect_value(RRF_RT_REG_QWORD); + expect_value(RRF_RT_DWORD); + expect_value(RRF_RT_QWORD); + expect_value(RRF_RT_ANY); + expect_value(RRF_NOEXPAND); + expect_value(RRF_ZEROONFAILURE); + + test_proc_comment("winnt.h"); + expect_value_32(HKEY_CLASSES_ROOT); + expect_value_32(HKEY_CURRENT_USER); + expect_value_32(HKEY_LOCAL_MACHINE); + expect_value_32(HKEY_USERS); + expect_value_32(HKEY_PERFORMANCE_DATA); + expect_value_32(HKEY_PERFORMANCE_TEXT); + expect_value_32(HKEY_PERFORMANCE_NLSTEXT); + expect_value_32(HKEY_CURRENT_CONFIG); + expect_value_32(HKEY_DYN_DATA); + expect_value_32(HKEY_CURRENT_USER_LOCAL_SETTINGS); + + expect_value(DELETE); + expect_value(READ_CONTROL); + expect_value(WRITE_DAC); + expect_value(WRITE_OWNER); + expect_value(SYNCHRONIZE); + + expect_value(KEY_QUERY_VALUE); + expect_value(KEY_SET_VALUE); + expect_value(KEY_CREATE_SUB_KEY); + expect_value(KEY_ENUMERATE_SUB_KEYS); + expect_value(KEY_NOTIFY); + expect_value(KEY_CREATE_LINK); + expect_value(KEY_WOW64_32KEY); + expect_value(KEY_WOW64_64KEY); + expect_value(KEY_WOW64_RES); + expect_value(KEY_READ); + expect_value(KEY_WRITE); + expect_value(KEY_EXECUTE); + expect_value(KEY_ALL_ACCESS); + + // RegQueryInfoKey + test_proc_end(); +} + +static void verify_verrsrc(ofstream& out) { + test_proc_begin(); + test_proc_comment("verrsrc.h"); + //expect_value_64(VS_FILE_INFO); + expect_value(VS_VERSION_INFO); + expect_value(VS_USER_DEFINED); + expect_size(VS_FIXEDFILEINFO); + // expect_value(VS_FF_DEBUG); + // expect_value(VS_FF_PRERELEASE); + // expect_value(VS_FF_PATCHED); + expect_value(VS_FFI_SIGNATURE); + // VFF_DEBUG + // VFT_WINDOWS_DRV + // VFT_WINDOWS_DLL + test_proc_end(); +} + +static void verify_error_codes(ofstream& out) { + test_proc_begin(); + test_proc_comment("winerror.h"); + + expect_value(ERROR_SUCCESS); + expect_value(NO_ERROR); + expect_value(SEC_E_OK); + out << endl; + expect_value(ERROR_INVALID_FUNCTION); + expect_value(ERROR_FILE_NOT_FOUND); + expect_value(ERROR_PATH_NOT_FOUND); + expect_value(ERROR_ACCESS_DENIED); + expect_value(ERROR_INVALID_HANDLE); + expect_value(ERROR_NOT_ENOUGH_MEMORY); + expect_value(ERROR_INVALID_BLOCK); + expect_value(ERROR_BAD_ENVIRONMENT); + expect_value(ERROR_BAD_FORMAT); + expect_value(ERROR_INVALID_ACCESS); + expect_value(ERROR_INVALID_DATA); + expect_value(ERROR_OUTOFMEMORY); + expect_value(ERROR_INVALID_DRIVE); + expect_value(ERROR_CURRENT_DIRECTORY); + expect_value(ERROR_NO_MORE_FILES); + expect_value(ERROR_SHARING_VIOLATION); + expect_value(ERROR_LOCK_VIOLATION); + expect_value(ERROR_HANDLE_EOF); + expect_value(ERROR_NOT_SUPPORTED); + expect_value(ERROR_FILE_EXISTS); + expect_value(ERROR_INVALID_PARAMETER); + expect_value(ERROR_BROKEN_PIPE); + expect_value(ERROR_CALL_NOT_IMPLEMENTED); + expect_value(ERROR_INSUFFICIENT_BUFFER); + expect_value(ERROR_INVALID_NAME); + expect_value(ERROR_BAD_ARGUMENTS); + expect_value(ERROR_LOCK_FAILED); + expect_value(ERROR_ALREADY_EXISTS); + expect_value(ERROR_NO_DATA); + expect_value(ERROR_ENVVAR_NOT_FOUND); + expect_value(ERROR_OPERATION_ABORTED); + expect_value(ERROR_IO_PENDING); + expect_value(ERROR_NO_UNICODE_TRANSLATION); + expect_value(ERROR_TIMEOUT); + expect_value(ERROR_DATATYPE_MISMATCH); + expect_value(ERROR_UNSUPPORTED_TYPE); + expect_value(ERROR_NOT_SAME_OBJECT); + expect_value(ERROR_PIPE_CONNECTED); + expect_value(ERROR_PIPE_BUSY); + out << endl; + expect_value(S_OK); + expect_value(E_NOTIMPL); + expect_value(E_NOINTERFACE); + expect_value(E_POINTER); + expect_value(E_ABORT); + expect_value(E_FAIL); + expect_value(E_UNEXPECTED); + expect_value(E_ACCESSDENIED); + expect_value(E_HANDLE); + expect_value(E_OUTOFMEMORY); + expect_value(E_INVALIDARG); + // out << endl; + // expect_value(SEVERITY_SUCCESS); + // expect_value(SEVERITY_ERROR); + // out << endl; + // expect_value(FACILITY_NULL); + test_proc_end(); +} + +static void verify_error_helpers(ofstream& out) { + test_proc_begin(); + test_proc_comment("winerror.h"); + + expect_value(SUCCEEDED(-1)); + expect_value(SUCCEEDED(0)); + expect_value(SUCCEEDED(1)); + out << endl; + expect_value(FAILED(-1)); + expect_value(FAILED(0)); + expect_value(FAILED(1)); + out << endl; + expect_value(IS_ERROR(-1)); + expect_value(IS_ERROR(0)); + expect_value(IS_ERROR(1)); + out << endl; + expect_value(HRESULT_CODE(0xFFFFCCCC)); + expect_value(HRESULT_FACILITY(0xFFFFCCCC)); + expect_value(HRESULT_SEVERITY(0x12345678)); + expect_value(HRESULT_SEVERITY(0x87654321)); + out << endl; + expect_value(MAKE_HRESULT(1, 2, 3)); + + test_proc_end(); +} + +static void test_core_sys_windows(ofstream& out) { + out << "//+build windows" << endl + << "package " << __func__ + << " // generated by " << path(__FILE__).filename().replace_extension("").string() << endl + << endl + << "import \"core:testing\"" << endl + << "import win32 \"core:sys/windows\"" << endl; + verify_win32_type_sizes(out); + verify_macros(out); + verify_winnt(out); + verify_winuser(out); + verify_gdi32(out); + verify_winmm(out); + verify_advapi32(out); + verify_winnls(out); + verify_winreg(out); + verify_verrsrc(out); + verify_error_codes(out); + verify_error_helpers(out); +} + +int main(int argc, char* argv[]) { + if (argc < 2) { cout << "Usage: " << path(argv[0]).filename().string() << " " << endl; return -1; } + auto filepath = path(argv[1]); + cout << "Writing " << filepath.string() << endl; + ofstream out(filepath); + test_core_sys_windows(out); + out.close(); +} diff --git a/tests/core/sys/windows/win32gen/win32gen.vcxproj b/tests/core/sys/windows/win32gen/win32gen.vcxproj new file mode 100644 index 000000000..33c44c989 --- /dev/null +++ b/tests/core/sys/windows/win32gen/win32gen.vcxproj @@ -0,0 +1,142 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {a31ceb6a-3f6f-4db6-82ce-8892efa48982} + win32console + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/core/sys/windows/win32gen/win32gen.vcxproj.filters b/tests/core/sys/windows/win32gen/win32gen.vcxproj.filters new file mode 100644 index 000000000..30cdcd8c6 --- /dev/null +++ b/tests/core/sys/windows/win32gen/win32gen.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index aeae44ca1..c408bc582 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -6,6 +6,43 @@ import dt "core:time/datetime" is_leap_year :: time.is_leap_year +@test +test_time_and_date_formatting :: proc(t: ^testing.T) { + buf: [64]u8 + { + now := time.Time{_nsec=min(i64)} // 1677-09-21 00:12:44.145224192 +0000 UTC + d := time.Duration(now._nsec) + + testing.expect_value(t, time.to_string_hms (now, buf[:]), "00:12:44") + testing.expect_value(t, time.to_string_hms_12 (now, buf[:]), "00:12:44 am") + testing.expect_value(t, time.to_string_hms_12 (now, buf[:], {"㏂", "㏘"}), "00:12:44㏂") + testing.expect_value(t, time.to_string_hms (d, buf[:]), "00:12:44") + + testing.expect_value(t, time.to_string_yyyy_mm_dd(now, buf[:]), "1677-09-21") + testing.expect_value(t, time.to_string_yy_mm_dd (now, buf[:]), "77-09-21") + testing.expect_value(t, time.to_string_dd_mm_yyyy(now, buf[:]), "21-09-1677") + testing.expect_value(t, time.to_string_dd_mm_yy (now, buf[:]), "21-09-77") + testing.expect_value(t, time.to_string_mm_dd_yyyy(now, buf[:]), "09-21-1677") + testing.expect_value(t, time.to_string_mm_dd_yy (now, buf[:]), "09-21-77") + } + { + now := time.Time{_nsec=max(i64)} // 2262-04-11 23:47:16.854775807 +0000 UTC + d := time.Duration(now._nsec) + + testing.expect_value(t, time.to_string_hms (now, buf[:]), "23:47:16") + testing.expect_value(t, time.to_string_hms_12 (now, buf[:]), "11:47:16 pm") + testing.expect_value(t, time.to_string_hms_12 (now, buf[:], {"㏂", "㏘"}), "11:47:16㏘") + testing.expect_value(t, time.to_string_hms (d, buf[:]), "23:47:16") + + testing.expect_value(t, time.to_string_yyyy_mm_dd(now, buf[:]), "2262-04-11") + testing.expect_value(t, time.to_string_yy_mm_dd (now, buf[:]), "62-04-11") + testing.expect_value(t, time.to_string_dd_mm_yyyy(now, buf[:]), "11-04-2262") + testing.expect_value(t, time.to_string_dd_mm_yy (now, buf[:]), "11-04-62") + testing.expect_value(t, time.to_string_mm_dd_yyyy(now, buf[:]), "04-11-2262") + testing.expect_value(t, time.to_string_mm_dd_yy (now, buf[:]), "04-11-62") + } +} + @test test_ordinal_date_roundtrip :: proc(t: ^testing.T) { testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 8a798d6c5..ce1849e1c 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -439,7 +439,7 @@ main :: proc() { } save_path := fmt.tprintf("verify/test_%v_%v.odin", test.package_name, code_test_name) - test_file_handle, err := os.open(save_path, os.O_WRONLY | os.O_CREATE); if err != 0 { + test_file_handle, err := os.open(save_path, os.O_WRONLY | os.O_CREATE); if err != nil { fmt.eprintf("We could not open the file to the path %q for writing\n", save_path) g_bad_doc = true continue diff --git a/tests/internal/test_map.odin b/tests/internal/test_map.odin index 9bd5d34ea..4d305024e 100644 --- a/tests/internal/test_map.odin +++ b/tests/internal/test_map.odin @@ -317,3 +317,23 @@ set_delete_random_key_value :: proc(t: ^testing.T) { seed_incr += 1 } } + +@test +test_union_key_should_not_be_hashing_specifc_variant :: proc(t: ^testing.T) { + Vec2 :: [2]f32 + BoneId :: distinct int + VertexId :: distinct int + Id :: union { + BoneId, + VertexId, + } + + m: map[Id]Vec2 + defer delete(m) + + bone_1: BoneId = 69 + m[bone_1] = {4, 20} + + testing.expect_value(t, bone_1 in m, true) + testing.expect_value(t, Id(bone_1) in m, true) +} diff --git a/tests/vendor/all.odin b/tests/vendor/all.odin index 1ce56e786..1abbc5d7f 100644 --- a/tests/vendor/all.odin +++ b/tests/vendor/all.odin @@ -1,3 +1,4 @@ package tests_vendor @(require) import "glfw" +@(require) import "lua/5.4" \ No newline at end of file diff --git a/tests/vendor/lua/5.4/factorial.lua b/tests/vendor/lua/5.4/factorial.lua new file mode 100644 index 000000000..00cfb20f7 --- /dev/null +++ b/tests/vendor/lua/5.4/factorial.lua @@ -0,0 +1,10 @@ +-- defines a factorial function +function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end +end + +return fact(10) \ No newline at end of file diff --git a/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin b/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin new file mode 100644 index 000000000..e331200ea --- /dev/null +++ b/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin @@ -0,0 +1,71 @@ +//+build windows, linux, darwin +package test_vendor_lua_54 + +import "core:testing" +import "core:c" +import lua "vendor:lua/5.4" +import "base:runtime" + +@(test) +// Test context.allocator and returning a string +return_string_with_context_based_allocator :: proc(t: ^testing.T) { + _context := context + + state: ^lua.State + state = lua.newstate(lua_context_allocator, &_context) + defer lua.close(state) + + lua.L_dostring(state, "return 'somestring'") + str := lua.tostring(state, -1) + + testing.expectf( + t, str == "somestring", "Expected Lua to return \"somestring\"", + ) +} + +@(test) +// Test lua.dofile and returning an integer +dofile_factorial :: proc(t: ^testing.T) { + state := lua.L_newstate() + defer lua.close(state) + + FACT_10 :: 3628800 + + res := lua.L_dofile(state, #directory + "/factorial.lua") + testing.expectf(t, lua.Status(res) == .OK, "Expected L_dofile to return OKAY") + + fact := lua.L_checkinteger(state, -1) + + testing.expectf(t, fact == FACT_10, "Expected factorial(10) to return %v, got %v", FACT_10, fact) +} + +@(test) +// Test that our bindings didn't get out of sync with the API version +verify_lua_api_version :: proc(t: ^testing.T) { + state := lua.L_newstate() + defer lua.close(state) + + version := int(lua.version(state)) + + testing.expectf(t, version == lua.VERSION_NUM, "Expected lua.version to return %v, got %v", lua.VERSION_NUM, version) +} + +// Simple context.allocator-based callback for Lua. Use `lua.newstate` to pass the context as user data. +lua_context_allocator :: proc "c" (ud: rawptr, ptr: rawptr, osize, nsize: c.size_t) -> (buf: rawptr) { + old_size := int(osize) + new_size := int(nsize) + context = (^runtime.Context)(ud)^ + + if ptr == nil { + data, err := runtime.mem_alloc(new_size) + return raw_data(data) if err == .None else nil + } else { + if nsize > 0 { + data, err := runtime.mem_resize(ptr, old_size, new_size) + return raw_data(data) if err == .None else nil + } else { + runtime.mem_free(ptr) + return + } + } +} \ No newline at end of file diff --git a/vendor/compress/lz4/lib/liblz4_static.lib b/vendor/compress/lz4/lib/liblz4_static.lib new file mode 100644 index 000000000..b60a626a2 Binary files /dev/null and b/vendor/compress/lz4/lib/liblz4_static.lib differ diff --git a/vendor/compress/lz4/lz4.odin b/vendor/compress/lz4/lz4.odin new file mode 100644 index 000000000..310248d56 --- /dev/null +++ b/vendor/compress/lz4/lz4.odin @@ -0,0 +1,542 @@ +package vendor_compress_lz4 + +when ODIN_OS == .Windows { + @(extra_linker_flags="/NODEFAULTLIB:libcmt") + foreign import lib "lib/liblz4_static.lib" +} + +import "core:c" + +VERSION_MAJOR :: 1 /* for breaking interface changes */ +VERSION_MINOR :: 10 /* for new (non-breaking) interface capabilities */ +VERSION_RELEASE :: 0 /* for tweaks, bug-fixes, or development */ + +VERSION_NUMBER :: VERSION_MAJOR *100*100 + VERSION_MINOR *100 + VERSION_RELEASE + +MEMORY_USAGE_MIN :: 10 +MEMORY_USAGE_DEFAULT :: 14 +MEMORY_USAGE_MAX :: 20 + +MEMORY_USAGE :: MEMORY_USAGE_DEFAULT + +MAX_INPUT_SIZE :: 0x7E000000 /* 2_113_929_216 bytes */ + + +COMPRESSBOUND :: #force_inline proc "c" (isize: c.int) -> c.int { + return u32(isize) > MAX_INPUT_SIZE ? 0 : isize + (isize/255) + 16 +} + + +DECODER_RING_BUFFER_SIZE :: #force_inline proc "c" (maxBlockSize: c.int) -> c.int { + return 65536 + 14 + maxBlockSize /* for static allocation; maxBlockSize presumed valid */ +} + +@(default_calling_convention="c", link_prefix="LZ4_") +foreign lib { + versionNumber :: proc() -> c.int --- /**< library version number; useful to check dll version; requires v1.3.0+ */ + versionString :: proc() -> cstring --- /**< library version string; useful to check dll version; requires v1.7.5+ */ + + /*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ + compress_default :: proc(src, dst: [^]byte, srcSize, dstCapacity: c.int) -> c.int --- + + /*! LZ4_decompress_safe() : + * @compressedSize : is the exact complete size of the compressed block. + * @dstCapacity : is the size of destination buffer (which must be already allocated), + * presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ + decompress_safe :: proc(src, dst: [^]byte, compressedSize, dstCapacity: c.int) -> c.int --- + + + /*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) + */ + compressBound :: proc(inputSize: c.int) -> c.int --- + + /*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). + */ + compress_fast :: proc(src, dst: [^]byte, srcSize, dstCapacity: c.int, acceleration: c.int) -> c.int --- + + + /*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ + sizeofState :: proc() -> c.int --- + compress_fast_extState :: proc (state: rawptr, src, dst: [^]byte, srcSize, dstCapacity: c.int, acceleration: c.int) -> c.int --- + + + /*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'dstCapacity'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : in+out parameter. Initially contains size of input. + * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ + compress_destSize :: proc(src, dst: [^]byte, srcSizePtr: ^c.int, targetDstSize: c.int) -> c.int --- + + + /*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ + decompress_safe_partial :: proc (src, dst: [^]byte, srcSize, targetOutputSize, dstCapacity: c.int) -> c.int --- + + + createStream :: proc() -> ^stream_t --- + freeStream :: proc(streamPtr: ^stream_t) -> c.int --- + + /*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ + resetStream_fast :: proc(streamPtr: ^stream_t) --- + + + /*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic. + * When in doubt, employ the Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ + loadDict :: proc(streamPtr: ^stream_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_loadDictSlow() : v1.10.0+ + * Same as LZ4_loadDict(), + * but uses a bit more cpu to reference the dictionary content more thoroughly. + * This is expected to slightly improve compression ratio. + * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ + loadDictSlow :: proc(streamPtr: ^stream_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_attach_dictionary() : stable since v1.10.0 + * + * This allows efficient re-use of a static dictionary multiple times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references @dictionaryStream in-place. + * + * Several assumptions are made about the state of @dictionaryStream. + * Currently, only states which have been prepared by LZ4_loadDict() or + * LZ4_loadDictSlow() should be expected to work. + * + * Alternatively, the provided @dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. + * @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the compression session. + * + * Note: there is no equivalent LZ4_attach_*() method on the decompression side + * because there is no initialization cost, hence no need to share the cost across multiple sessions. + * To decompress LZ4 blocks using dictionary, attached or not, + * just employ the regular LZ4_setStreamDecode() for streaming, + * or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression. + */ + attach_dictionary :: proc(workingStream, dictionaryStream: ^stream_t) --- + + /*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ + compress_fast_continue :: proc(streamPtr: ^stream_t, src, dst: [^]byte, srcSize, dstCapacity: c.int, acceleration: c.int) -> c.int --- + + /*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ + saveDict :: proc(streamPtr: ^stream_t, safeBuffer: [^]byte, maxDictSize: c.int) -> c.int --- + + + createStreamDecode :: proc() -> ^streamDecode_t --- + freeStreamDecode :: proc(LZ4_stream: ^streamDecode_t) -> c.int --- + + /*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ + setStreamDecode :: proc(LZ4_streamDecode: ^streamDecode_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ + decoderRingBufferSize :: proc(maxBlockSize: c.int) -> c.int --- + + /*! LZ4_decompress_safe_continue() : + * This decoding function allows decompression of consecutive blocks in "streaming" mode. + * The difference with the usual independent blocks is that + * new blocks are allowed to find references into former blocks. + * A block is an unsplittable entity, and must be presented entirely to the decompression function. + * LZ4_decompress_safe_continue() only accepts one block at a time. + * It's modeled after `LZ4_decompress_safe()` and behaves similarly. + * + * @LZ4_streamDecode : decompression state, tracking the position in memory of past data + * @compressedSize : exact complete size of one compressed block. + * @dstCapacity : size of destination buffer (which must be already allocated), + * must be an upper bound of decompressed size. + * @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * + * The last 64KB of previously decoded data *must* remain available and unmodified + * at the memory position where they were previously decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. + */ + decompress_safe_continue :: proc(LZ4_streamDecode: ^streamDecode_t, src, dst: [^]byte, srcSize, dstCapacity: c.int) -> c.int --- + + + /*! LZ4_decompress_safe_usingDict() : + * Works the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue() + * However, it's stateless: it doesn't need any LZ4_streamDecode_t state. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ + decompress_safe_usingDict :: proc(src, dst: [^]byte, srcSize, dstCapacity: c.int, dictStart: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_decompress_safe_partial_usingDict() : + * Behaves the same as LZ4_decompress_safe_partial() + * with the added ability to specify a memory segment for past data. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ + decompress_safe_partial_usingDict :: proc(src, dst: [^]byte, compressedSize, targetOutputSize, maxOutputSize: c.int, dictStart: [^]byte, dictSize: c.int) -> c.int --- + +} + + +STREAM_MINSIZE :: (1 << MEMORY_USAGE) + 32 /* static size, for inter-version compatibility */ + +stream_t :: struct #raw_union { + minStateSize: [STREAM_MINSIZE]byte, + internal_donotuse: stream_t_internal, +} + + +HASHLOG :: MEMORY_USAGE-2 +HASHTABLESIZE :: 1 << MEMORY_USAGE +HASH_SIZE_U32 :: 1 << HASHLOG /* required as macro for static allocation */ + +stream_t_internal :: struct { + hashTable: [HASH_SIZE_U32]u32, + dictionary: [^]byte, + dictCtx: ^stream_t_internal, + currentOffset: u32, + tableType: u32, + dictSize: u32, + /* Implicit padding to ensure structure is aligned */ +} + + +STREAMDECODE_MINSIZE :: 32 +streamDecode_t :: struct #raw_union { + minStateSize: [STREAMDECODE_MINSIZE]byte, + internal_donotuse: streamDecode_t_internal, +} + +streamDecode_t_internal :: struct { + externalDict: [^]byte, + prefixEnd: [^]byte, + extDictSize: c.size_t, + prefixSize: c.size_t, +} + + + +/////////////////// +// lz4hc + +CLEVEL_MIN :: 2 +CLEVEL_DEFAULT :: 9 +CLEVEL_OPT_MIN :: 10 +CLEVEL_MAX :: 12 + + +@(default_calling_convention="c", link_prefix="LZ4_") +foreign lib { + /*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ + compress_HC :: proc(src, dst: [^]byte, srcSize, dstCapacity, compressionLevel: c.int) -> c.int --- + + + /*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ + sizeofStateHC :: proc() -> c.int --- + compress_HC_extStateHC :: proc(stateHC: rawptr, src, dst: [^]byte, srcSize, maxDstSize: c.int, compressionLevel: c.int) -> c.int --- + + + /*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ + compress_HC_destSize :: proc(stateHC: rawptr, src, dst: [^]byte, srcSizePtr: ^c.int, targetDstSize: c.int, compressionLevel: c.int) -> c.int --- + + /*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. + */ + createStreamHC :: proc() -> ^streamHC_t --- + freeStreamHC :: proc(streamHCPtr: ^streamHC_t) -> c.int --- + + resetStreamHC_fast :: proc(streamHCPtr: ^streamHC_t, compressionLevel: c.int) --- /* v1.9.0+ */ + loadDictHC :: proc(streamHCPtr: ^streamHC_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + compress_HC_continue :: proc(streamHCPtr: ^streamHC_t, src, dst: [^]byte, srcSize, maxDstSize: c.int) -> c.int --- + + /*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ + compress_HC_continue_destSize:: proc(LZ4_streamHCPtr: ^streamHC_t, src, dst: [^]byte, srcSizePtr: ^c.int, targetDstSize: c.int) -> c.int --- + + saveDictHC :: proc(streamHCPtr: ^streamHC_t, safeBuffer: [^]byte, maxDictSize: c.int) -> c.int --- + + /*! LZ4_attach_HC_dictionary() : stable since v1.10.0 + * This API allows for the efficient re-use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ + attach_HC_dictionary :: proc(working_stream, dictionary_stream: ^streamHC_t) --- +} + + +HC_DICTIONARY_LOGSIZE :: 16 +HC_MAXD :: 1< /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*! LZ4_FREESTANDING : + * When this macro is set to 1, it enables "freestanding mode" that is + * suitable for typical freestanding environment which doesn't support + * standard C library. + * + * - LZ4_FREESTANDING is a compile-time switch. + * - It requires the following macros to be defined: + * LZ4_memcpy, LZ4_memmove, LZ4_memset. + * - It only enables LZ4/HC functions which don't use heap. + * All LZ4F_* functions are not supported. + * - See tests/freestanding.c to check its basic setup. + */ +#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1) +# define LZ4_HEAPMODE 0 +# define LZ4HC_HEAPMODE 0 +# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1 +# if !defined(LZ4_memcpy) +# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'." +# endif +# if !defined(LZ4_memset) +# error "LZ4_FREESTANDING requires macro 'LZ4_memset'." +# endif +# if !defined(LZ4_memmove) +# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'." +# endif +#elif ! defined(LZ4_FREESTANDING) +# define LZ4_FREESTANDING 0 +#endif + + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 10 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */ + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */ + + +/*-************************************ +* Tuning memory usage +**************************************/ +/*! + * LZ4_MEMORY_USAGE : + * Can be selected at compile time, by setting LZ4_MEMORY_USAGE. + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB) + * Increasing memory usage improves compression ratio, generally at the cost of speed. + * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into most L1 caches. + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT +#endif + +/* These are absolute limits, they should not be changed by users */ +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 14 +#define LZ4_MEMORY_USAGE_MAX 20 + +#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) +# error "LZ4_MEMORY_USAGE is too small !" +#endif + +#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX) +# error "LZ4_MEMORY_USAGE is too large !" +#endif + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + * @compressedSize : is the exact complete size of the compressed block. + * @dstCapacity : is the size of destination buffer (which must be already allocated), + * presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'dstCapacity'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : in+out parameter. Initially contains size of input. + * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ +LZ4LIB_API int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize); + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/*! + Note about RC_INVOKED + + - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio). + https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros + + - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars) + and reports warning "RC4011: identifier truncated". + + - To eliminate the warning, we surround long preprocessor symbol with + "#if !defined(RC_INVOKED) ... #endif" block that means + "skip this block when rc.exe is trying to read it". +*/ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic. + * When in doubt, employ the Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_loadDictSlow() : v1.10.0+ + * Same as LZ4_loadDict(), + * but uses a bit more cpu to reference the dictionary content more thoroughly. + * This is expected to slightly improve compression ratio. + * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ +LZ4LIB_API int LZ4_loadDictSlow(LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_attach_dictionary() : stable since v1.10.0 + * + * This allows efficient re-use of a static dictionary multiple times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references @dictionaryStream in-place. + * + * Several assumptions are made about the state of @dictionaryStream. + * Currently, only states which have been prepared by LZ4_loadDict() or + * LZ4_loadDictSlow() should be expected to work. + * + * Alternatively, the provided @dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. + * @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the compression session. + * + * Note: there is no equivalent LZ4_attach_*() method on the decompression side + * because there is no initialization cost, hence no need to share the cost across multiple sessions. + * To decompress LZ4 blocks using dictionary, attached or not, + * just employ the regular LZ4_setStreamDecode() for streaming, + * or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression. + */ +LZ4LIB_API void +LZ4_attach_dictionary(LZ4_stream_t* workingStream, + const LZ4_stream_t* dictionaryStream); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_safe_continue() : + * This decoding function allows decompression of consecutive blocks in "streaming" mode. + * The difference with the usual independent blocks is that + * new blocks are allowed to find references into former blocks. + * A block is an unsplittable entity, and must be presented entirely to the decompression function. + * LZ4_decompress_safe_continue() only accepts one block at a time. + * It's modeled after `LZ4_decompress_safe()` and behaves similarly. + * + * @LZ4_streamDecode : decompression state, tracking the position in memory of past data + * @compressedSize : exact complete size of one compressed block. + * @dstCapacity : size of destination buffer (which must be already allocated), + * must be an upper bound of decompressed size. + * @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * + * The last 64KB of previously decoded data *must* remain available and unmodified + * at the memory position where they were previously decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int +LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* src, char* dst, + int srcSize, int dstCapacity); + + +/*! LZ4_decompress_safe_usingDict() : + * Works the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue() + * However, it's stateless: it doesn't need any LZ4_streamDecode_t state. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int +LZ4_decompress_safe_usingDict(const char* src, char* dst, + int srcSize, int dstCapacity, + const char* dictStart, int dictSize); + +/*! LZ4_decompress_safe_partial_usingDict() : + * Behaves the same as LZ4_decompress_safe_partial() + * with the added ability to specify a memory segment for past data. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int +LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, + int compressedSize, + int targetOutputSize, int maxOutputSize, + const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +# define LZ4LIB_STATIC_API LZ4LIB_API +#else +# define LZ4LIB_STATIC_API +#endif + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_compress_destSize_extState() : introduced in v1.10.0 + * Same as LZ4_compress_destSize(), but using an externally allocated state. + * Also: exposes @acceleration + */ +int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration); + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly constrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. + */ + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * Private Definitions + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose user code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef int8_t LZ4_i8; + typedef uint8_t LZ4_byte; + typedef uint16_t LZ4_u16; + typedef uint32_t LZ4_u32; +#else + typedef signed char LZ4_i8; + typedef unsigned char LZ4_byte; + typedef unsigned short LZ4_u16; + typedef unsigned int LZ4_u32; +#endif + +/*! LZ4_stream_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_stream_t object. +**/ + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + const LZ4_byte* dictionary; + const LZ4_stream_t_internal* dictCtx; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + LZ4_u32 dictSize; + /* Implicit padding to ensure structure is aligned */ +}; + +#define LZ4_STREAM_MINSIZE ((1UL << (LZ4_MEMORY_USAGE)) + 32) /* static size, for inter-version compatibility */ +union LZ4_stream_u { + char minStateSize[LZ4_STREAM_MINSIZE]; + LZ4_stream_t_internal internal_donotuse; +}; /* previously typedef'd to LZ4_stream_t */ + + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead +**/ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* stateBuffer, size_t size); + + +/*! LZ4_streamDecode_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_streamDecode_t object. +**/ +typedef struct { + const LZ4_byte* externalDict; + const LZ4_byte* prefixEnd; + size_t extDictSize; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#define LZ4_STREAMDECODE_MINSIZE 32 +union LZ4_streamDecode_u { + char minStateSize[LZ4_STREAMDECODE_MINSIZE]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# else +# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") +# define LZ4_DEPRECATED(message) /* disabled */ +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/*! Obsolete compression functions (since v1.7.3) */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/*! Obsolete decompression functions (since v1.8.0) */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions (since v1.7.0) + * degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/*! Obsolete streaming decoding functions (since v1.7.0) */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : + * These functions used to be faster than LZ4_decompress_safe(), + * but this is no longer the case. They are now slower. + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality can be achieved in a more secure manner + * by employing LZ4_decompress_safe_partial(). + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider migrating towards LZ4_decompress_safe_continue() instead. " + "Note that the contract will change (requires block's compressed size, instead of decompressed size)") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_98237428734687 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/vendor/compress/lz4/src/lz4frame.h b/vendor/compress/lz4/src/lz4frame.h new file mode 100644 index 000000000..b8ae32276 --- /dev/null +++ b/vendor/compress/lz4/src/lz4frame.h @@ -0,0 +1,751 @@ +/* + LZ4F - LZ4-Frame library + Header File + Copyright (C) 2011-2020, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +/* LZ4F is a stand-alone API able to create and decode LZ4 frames + * conformant with specification v1.6.1 in doc/lz4_Frame_format.md . + * Generated frames are compatible with `lz4` CLI. + * + * LZ4F also offers streaming capabilities. + * + * lz4.h is not required when using lz4frame.h, + * except to extract common constants such as LZ4_VERSION_NUMBER. + * */ + +#ifndef LZ4F_H_09782039843 +#define LZ4F_H_09782039843 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + * Introduction + * + * lz4frame.h implements LZ4 frame specification: see doc/lz4_Frame_format.md . + * LZ4 Frames are compatible with `lz4` CLI, + * and designed to be interoperable with any system. +**/ + +/*-*************************************************************** + * Compiler specifics + *****************************************************************/ +/* LZ4_DLL_EXPORT : + * Enable exporting of functions when building a Windows DLL + * LZ4FLIB_VISIBILITY : + * Control library symbols visibility. + */ +#ifndef LZ4FLIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4FLIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4FLIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4FLIB_API __declspec(dllexport) LZ4FLIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4FLIB_API __declspec(dllimport) LZ4FLIB_VISIBILITY +#else +# define LZ4FLIB_API LZ4FLIB_VISIBILITY +#endif + +#ifdef LZ4F_DISABLE_DEPRECATE_WARNINGS +# define LZ4F_DEPRECATE(x) x +#else +# if defined(_MSC_VER) +# define LZ4F_DEPRECATE(x) x /* __declspec(deprecated) x - only works with C++ */ +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +# define LZ4F_DEPRECATE(x) x __attribute__((deprecated)) +# else +# define LZ4F_DEPRECATE(x) x /* no deprecation warning for this compiler */ +# endif +#endif + + +/*-************************************ + * Error management + **************************************/ +typedef size_t LZ4F_errorCode_t; + +LZ4FLIB_API unsigned LZ4F_isError(LZ4F_errorCode_t code); /**< tells when a function result is an error code */ +LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /**< return error code string; for debugging */ + + +/*-************************************ + * Frame compression types + ************************************* */ +/* #define LZ4F_ENABLE_OBSOLETE_ENUMS // uncomment to enable obsolete enums */ +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +# define LZ4F_OBSOLETE_ENUM(x) , LZ4F_DEPRECATE(x) = LZ4F_##x +#else +# define LZ4F_OBSOLETE_ENUM(x) +#endif + +/* The larger the block size, the (slightly) better the compression ratio, + * though there are diminishing returns. + * Larger blocks also increase memory usage on both compression and decompression sides. + */ +typedef enum { + LZ4F_default=0, + LZ4F_max64KB=4, + LZ4F_max256KB=5, + LZ4F_max1MB=6, + LZ4F_max4MB=7 + LZ4F_OBSOLETE_ENUM(max64KB) + LZ4F_OBSOLETE_ENUM(max256KB) + LZ4F_OBSOLETE_ENUM(max1MB) + LZ4F_OBSOLETE_ENUM(max4MB) +} LZ4F_blockSizeID_t; + +/* Linked blocks sharply reduce inefficiencies when using small blocks, + * they compress better. + * However, some LZ4 decoders are only compatible with independent blocks */ +typedef enum { + LZ4F_blockLinked=0, + LZ4F_blockIndependent + LZ4F_OBSOLETE_ENUM(blockLinked) + LZ4F_OBSOLETE_ENUM(blockIndependent) +} LZ4F_blockMode_t; + +typedef enum { + LZ4F_noContentChecksum=0, + LZ4F_contentChecksumEnabled + LZ4F_OBSOLETE_ENUM(noContentChecksum) + LZ4F_OBSOLETE_ENUM(contentChecksumEnabled) +} LZ4F_contentChecksum_t; + +typedef enum { + LZ4F_noBlockChecksum=0, + LZ4F_blockChecksumEnabled +} LZ4F_blockChecksum_t; + +typedef enum { + LZ4F_frame=0, + LZ4F_skippableFrame + LZ4F_OBSOLETE_ENUM(skippableFrame) +} LZ4F_frameType_t; + +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +typedef LZ4F_blockSizeID_t blockSizeID_t; +typedef LZ4F_blockMode_t blockMode_t; +typedef LZ4F_frameType_t frameType_t; +typedef LZ4F_contentChecksum_t contentChecksum_t; +#endif + +/*! LZ4F_frameInfo_t : + * makes it possible to set or read frame parameters. + * Structure must be first init to 0, using memset() or LZ4F_INIT_FRAMEINFO, + * setting all parameters to default. + * It's then possible to update selectively some parameters */ +typedef struct { + LZ4F_blockSizeID_t blockSizeID; /* max64KB, max256KB, max1MB, max4MB; 0 == default (LZ4F_max64KB) */ + LZ4F_blockMode_t blockMode; /* LZ4F_blockLinked, LZ4F_blockIndependent; 0 == default (LZ4F_blockLinked) */ + LZ4F_contentChecksum_t contentChecksumFlag; /* 1: add a 32-bit checksum of frame's decompressed data; 0 == default (disabled) */ + LZ4F_frameType_t frameType; /* read-only field : LZ4F_frame or LZ4F_skippableFrame */ + unsigned long long contentSize; /* Size of uncompressed content ; 0 == unknown */ + unsigned dictID; /* Dictionary ID, sent by compressor to help decoder select correct dictionary; 0 == no dictID provided */ + LZ4F_blockChecksum_t blockChecksumFlag; /* 1: each block followed by a checksum of block's compressed data; 0 == default (disabled) */ +} LZ4F_frameInfo_t; + +#define LZ4F_INIT_FRAMEINFO { LZ4F_max64KB, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame, 0ULL, 0U, LZ4F_noBlockChecksum } /* v1.8.3+ */ + +/*! LZ4F_preferences_t : + * makes it possible to supply advanced compression instructions to streaming interface. + * Structure must be first init to 0, using memset() or LZ4F_INIT_PREFERENCES, + * setting all parameters to default. + * All reserved fields must be set to zero. */ +typedef struct { + LZ4F_frameInfo_t frameInfo; + int compressionLevel; /* 0: default (fast mode); values > LZ4HC_CLEVEL_MAX count as LZ4HC_CLEVEL_MAX; values < 0 trigger "fast acceleration" */ + unsigned autoFlush; /* 1: always flush; reduces usage of internal buffers */ + unsigned favorDecSpeed; /* 1: parser favors decompression speed vs compression ratio. Only works for high compression modes (>= LZ4HC_CLEVEL_OPT_MIN) */ /* v1.8.2+ */ + unsigned reserved[3]; /* must be zero for forward compatibility */ +} LZ4F_preferences_t; + +#define LZ4F_INIT_PREFERENCES { LZ4F_INIT_FRAMEINFO, 0, 0u, 0u, { 0u, 0u, 0u } } /* v1.8.3+ */ + + +/*-********************************* +* Simple compression function +***********************************/ + +/*! LZ4F_compressFrame() : + * Compress srcBuffer content into an LZ4-compressed frame. + * It's a one shot operation, all input content is consumed, and all output is generated. + * + * Note : it's a stateless operation (no LZ4F_cctx state needed). + * In order to reduce load on the allocator, LZ4F_compressFrame(), by default, + * uses the stack to allocate space for the compression state and some table. + * If this usage of the stack is too much for your application, + * consider compiling `lz4frame.c` with compile-time macro LZ4F_HEAPMODE set to 1 instead. + * All state allocations will use the Heap. + * It also means each invocation of LZ4F_compressFrame() will trigger several internal alloc/free invocations. + * + * @dstCapacity MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * @preferencesPtr is optional : one can provide NULL, in which case all preferences are set to default. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_preferences_t* preferencesPtr); + +/*! LZ4F_compressFrameBound() : + * Returns the maximum possible compressed size with LZ4F_compressFrame() given srcSize and preferences. + * `preferencesPtr` is optional. It can be replaced by NULL, in which case, the function will assume default preferences. + * Note : this result is only usable with LZ4F_compressFrame(). + * It may also be relevant to LZ4F_compressUpdate() _only if_ no flush() operation is ever performed. + */ +LZ4FLIB_API size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr); + + +/*! LZ4F_compressionLevel_max() : + * @return maximum allowed compression level (currently: 12) + */ +LZ4FLIB_API int LZ4F_compressionLevel_max(void); /* v1.8.0+ */ + + +/*-*********************************** +* Advanced compression functions +*************************************/ +typedef struct LZ4F_cctx_s LZ4F_cctx; /* incomplete type */ +typedef LZ4F_cctx* LZ4F_compressionContext_t; /* for compatibility with older APIs, prefer using LZ4F_cctx */ + +typedef struct { + unsigned stableSrc; /* 1 == src content will remain present on future calls to LZ4F_compress(); skip copying src content within tmp buffer */ + unsigned reserved[3]; +} LZ4F_compressOptions_t; + +/*--- Resource Management ---*/ + +#define LZ4F_VERSION 100 /* This number can be used to check for an incompatible API breaking change */ +LZ4FLIB_API unsigned LZ4F_getVersion(void); + +/*! LZ4F_createCompressionContext() : + * The first thing to do is to create a compressionContext object, + * which will keep track of operation state during streaming compression. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version, + * and a pointer to LZ4F_cctx*, to write the resulting pointer into. + * @version provided MUST be LZ4F_VERSION. It is intended to track potential version mismatch, notably when using DLL. + * The function provides a pointer to a fully allocated LZ4F_cctx object. + * @cctxPtr MUST be != NULL. + * If @return != zero, context creation failed. + * A created compression context can be employed multiple times for consecutive streaming operations. + * Once all streaming compression jobs are completed, + * the state object can be released using LZ4F_freeCompressionContext(). + * Note1 : LZ4F_freeCompressionContext() is always successful. Its return value can be ignored. + * Note2 : LZ4F_freeCompressionContext() works fine with NULL input pointers (do nothing). +**/ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_cctx** cctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_cctx* cctx); + + +/*---- Compression ----*/ + +#define LZ4F_HEADER_SIZE_MIN 7 /* LZ4 Frame header size can vary, depending on selected parameters */ +#define LZ4F_HEADER_SIZE_MAX 19 + +/* Size in bytes of a block header in little-endian format. Highest bit indicates if block data is uncompressed */ +#define LZ4F_BLOCK_HEADER_SIZE 4 + +/* Size in bytes of a block checksum footer in little-endian format. */ +#define LZ4F_BLOCK_CHECKSUM_SIZE 4 + +/* Size in bytes of the content checksum. */ +#define LZ4F_CONTENT_CHECKSUM_SIZE 4 + +/*! LZ4F_compressBegin() : + * will write the frame header into dstBuffer. + * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * `prefsPtr` is optional : NULL can be provided to set all preferences to default. + * @return : number of bytes written into dstBuffer for the header + * or an error code (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressBegin(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressBound() : + * Provides minimum dstCapacity required to guarantee success of + * LZ4F_compressUpdate(), given a srcSize and preferences, for a worst case scenario. + * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() instead. + * Note that the result is only valid for a single invocation of LZ4F_compressUpdate(). + * When invoking LZ4F_compressUpdate() multiple times, + * if the output buffer is gradually filled up instead of emptied and re-used from its start, + * one must check if there is enough remaining capacity before each invocation, using LZ4F_compressBound(). + * @return is always the same for a srcSize and prefsPtr. + * prefsPtr is optional : when NULL is provided, preferences will be set to cover worst case scenario. + * tech details : + * @return if automatic flushing is not enabled, includes the possibility that internal buffer might already be filled by up to (blockSize-1) bytes. + * It also includes frame footer (ending + checksum), since it might be generated by LZ4F_compressEnd(). + * @return doesn't include frame header, as it was already generated by LZ4F_compressBegin(). + */ +LZ4FLIB_API size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * Important rule: dstCapacity MUST be large enough to ensure operation success even in worst case situations. + * This value is provided by LZ4F_compressBound(). + * If this condition is not respected, LZ4F_compress() will fail (result is an errorCode). + * After an error, the state is left in a UB state, and must be re-initialized or freed. + * If previously an uncompressed block was written, buffered data is flushed + * before appending compressed data is continued. + * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. + * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_flush() : + * When data must be generated and sent immediately, without waiting for a block to be completely filled, + * it's possible to call LZ4_flush(). It will immediately compress any data buffered within cctx. + * `dstCapacity` must be large enough to ensure the operation will be successful. + * `cOptPtr` is optional : it's possible to provide NULL, all options will be set to default. + * @return : nb of bytes written into dstBuffer (can be zero, when there is no data stored within cctx) + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_flush() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + */ +LZ4FLIB_API size_t LZ4F_flush(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_compressEnd() : + * To properly finish an LZ4 frame, invoke LZ4F_compressEnd(). + * It will flush whatever data remained within `cctx` (like LZ4_flush()) + * and properly finalize the frame, with an endMark and a checksum. + * `cOptPtr` is optional : NULL can be provided, in which case all options will be set to default. + * @return : nb of bytes written into dstBuffer, necessarily >= 4 (endMark), + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_compressEnd() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + * A successful call to LZ4F_compressEnd() makes `cctx` available again for another compression task. + */ +LZ4FLIB_API size_t LZ4F_compressEnd(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + + +/*-********************************* +* Decompression functions +***********************************/ +typedef struct LZ4F_dctx_s LZ4F_dctx; /* incomplete type */ +typedef LZ4F_dctx* LZ4F_decompressionContext_t; /* compatibility with previous API versions */ + +typedef struct { + unsigned stableDst; /* pledges that last 64KB decompressed data is present right before @dstBuffer pointer. + * This optimization skips internal storage operations. + * Once set, this pledge must remain valid up to the end of current frame. */ + unsigned skipChecksums; /* disable checksum calculation and verification, even when one is present in frame, to save CPU time. + * Setting this option to 1 once disables all checksums for the rest of the frame. */ + unsigned reserved1; /* must be set to zero for forward compatibility */ + unsigned reserved0; /* idem */ +} LZ4F_decompressOptions_t; + + +/* Resource management */ + +/*! LZ4F_createDecompressionContext() : + * Create an LZ4F_dctx object, to track all decompression operations. + * @version provided MUST be LZ4F_VERSION. + * @dctxPtr MUST be valid. + * The function fills @dctxPtr with the value of a pointer to an allocated and initialized LZ4F_dctx object. + * The @return is an errorCode, which can be tested using LZ4F_isError(). + * dctx memory can be released using LZ4F_freeDecompressionContext(); + * Result of LZ4F_freeDecompressionContext() indicates current state of decompressionContext when being released. + * That is, it should be == 0 if decompression has been completed fully and correctly. + */ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** dctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx); + + +/*-*********************************** +* Streaming decompression functions +*************************************/ + +#define LZ4F_MAGICNUMBER 0x184D2204U +#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U +#define LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH 5 + +/*! LZ4F_headerSize() : v1.9.0+ + * Provide the header size of a frame starting at `src`. + * `srcSize` must be >= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH, + * which is enough to decode the header length. + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + * note : Frame header size is variable, but is guaranteed to be + * >= LZ4F_HEADER_SIZE_MIN bytes, and <= LZ4F_HEADER_SIZE_MAX bytes. + */ +LZ4FLIB_API size_t LZ4F_headerSize(const void* src, size_t srcSize); + +/*! LZ4F_getFrameInfo() : + * This function extracts frame parameters (max blockSize, dictID, etc.). + * Its usage is optional: user can also invoke LZ4F_decompress() directly. + * + * Extracted information will fill an existing LZ4F_frameInfo_t structure. + * This can be useful for allocation and dictionary identification purposes. + * + * LZ4F_getFrameInfo() can work in the following situations : + * + * 1) At the beginning of a new frame, before any invocation of LZ4F_decompress(). + * It will decode header from `srcBuffer`, + * consuming the header and starting the decoding process. + * + * Input size must be large enough to contain the full frame header. + * Frame header size can be known beforehand by LZ4F_headerSize(). + * Frame header size is variable, but is guaranteed to be >= LZ4F_HEADER_SIZE_MIN bytes, + * and not more than <= LZ4F_HEADER_SIZE_MAX bytes. + * Hence, blindly providing LZ4F_HEADER_SIZE_MAX bytes or more will always work. + * It's allowed to provide more input data than the header size, + * LZ4F_getFrameInfo() will only consume the header. + * + * If input size is not large enough, + * aka if it's smaller than header size, + * function will fail and return an error code. + * + * 2) After decoding has been started, + * it's possible to invoke LZ4F_getFrameInfo() anytime + * to extract already decoded frame parameters stored within dctx. + * + * Note that, if decoding has barely started, + * and not yet read enough information to decode the header, + * LZ4F_getFrameInfo() will fail. + * + * The number of bytes consumed from srcBuffer will be updated in *srcSizePtr (necessarily <= original value). + * LZ4F_getFrameInfo() only consumes bytes when decoding has not yet started, + * and when decoding the header has been successful. + * Decompression must then resume from (srcBuffer + *srcSizePtr). + * + * @return : a hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * or an error code which can be tested using LZ4F_isError(). + * note 1 : in case of error, dctx is not modified. Decoding operation can resume from beginning safely. + * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. + */ +LZ4FLIB_API size_t +LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr); + +/*! LZ4F_decompress() : + * Call this function repetitively to regenerate data compressed in `srcBuffer`. + * + * The function requires a valid dctx state. + * It will read up to *srcSizePtr bytes from srcBuffer, + * and decompress data into dstBuffer, of capacity *dstSizePtr. + * + * The nb of bytes consumed from srcBuffer will be written into *srcSizePtr (necessarily <= original value). + * The nb of bytes decompressed into dstBuffer will be written into *dstSizePtr (necessarily <= original value). + * + * The function does not necessarily read all input bytes, so always check value in *srcSizePtr. + * Unconsumed source data must be presented again in subsequent invocations. + * + * `dstBuffer` can freely change between each consecutive function invocation. + * `dstBuffer` content will be overwritten. + * + * Note: if `LZ4F_getFrameInfo()` is called before `LZ4F_decompress()`, srcBuffer must be updated to reflect + * the number of bytes consumed after reading the frame header. Failure to update srcBuffer before calling + * `LZ4F_decompress()` will cause decompression failure or, even worse, successful but incorrect decompression. + * See the `LZ4F_getFrameInfo()` docs for details. + * + * @return : an hint of how many `srcSize` bytes LZ4F_decompress() expects for next call. + * Schematically, it's the size of the current (or remaining) compressed block + header of next block. + * Respecting the hint provides some small speed benefit, because it skips intermediate buffers. + * This is just a hint though, it's always possible to provide any srcSize. + * + * When a frame is fully decoded, @return will be 0 (no more data expected). + * When provided with more bytes than necessary to decode a frame, + * LZ4F_decompress() will stop reading exactly at end of current frame, and @return 0. + * + * If decompression failed, @return is an error code, which can be tested using LZ4F_isError(). + * After a decompression error, the `dctx` context is not resumable. + * Use LZ4F_resetDecompressionContext() to return to clean state. + * + * After a frame is fully decoded, dctx can be used again to decompress another frame. + */ +LZ4FLIB_API size_t +LZ4F_decompress(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* dOptPtr); + + +/*! LZ4F_resetDecompressionContext() : added in v1.8.0 + * In case of an error, the context is left in "undefined" state. + * In which case, it's necessary to reset it, before re-using it. + * This method can also be used to abruptly stop any unfinished decompression, + * and start a new one using same context resources. */ +LZ4FLIB_API void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx); /* always successful */ + + +/********************************** + * Dictionary compression API + *********************************/ + +/* A Dictionary is useful for the compression of small messages (KB range). + * It dramatically improves compression efficiency. + * + * LZ4 can ingest any input as dictionary, though only the last 64 KB are useful. + * Better results are generally achieved by using Zstandard's Dictionary Builder + * to generate a high-quality dictionary from a set of samples. + * + * The same dictionary will have to be used on the decompression side + * for decoding to be successful. + * To help identify the correct dictionary at decoding stage, + * the frame header allows optional embedding of a dictID field. + */ + +/*! LZ4F_compressBegin_usingDict() : stable since v1.10 + * Inits dictionary compression streaming, and writes the frame header into dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @prefsPtr is optional : one may provide NULL as argument, + * however, it's the only way to provide dictID in the frame header. + * @dictBuffer must outlive the compression session. + * @return : number of bytes written into dstBuffer for the header, + * or an error code (which can be tested using LZ4F_isError()) + * NOTE: The LZ4Frame spec allows each independent block to be compressed with the dictionary, + * but this entry supports a more limited scenario, where only the first block uses the dictionary. + * This is still useful for small data, which only need one block anyway. + * For larger inputs, one may be more interested in LZ4F_compressFrame_usingCDict() below. + */ +LZ4FLIB_API size_t +LZ4F_compressBegin_usingDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* dictBuffer, size_t dictSize, + const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_decompress_usingDict() : stable since v1.10 + * Same as LZ4F_decompress(), using a predefined dictionary. + * Dictionary is used "in place", without any preprocessing. +** It must remain accessible throughout the entire frame decoding. */ +LZ4FLIB_API size_t +LZ4F_decompress_usingDict(LZ4F_dctx* dctxPtr, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const void* dict, size_t dictSize, + const LZ4F_decompressOptions_t* decompressOptionsPtr); + +/***************************************** + * Bulk processing dictionary compression + *****************************************/ + +/* Loading a dictionary has a cost, since it involves construction of tables. + * The Bulk processing dictionary API makes it possible to share this cost + * over an arbitrary number of compression jobs, even concurrently, + * markedly improving compression latency for these cases. + * + * Note that there is no corresponding bulk API for the decompression side, + * because dictionary does not carry any initialization cost for decompression. + * Use the regular LZ4F_decompress_usingDict() there. + */ +typedef struct LZ4F_CDict_s LZ4F_CDict; + +/*! LZ4_createCDict() : stable since v1.10 + * When compressing multiple messages / blocks using the same dictionary, it's recommended to initialize it just once. + * LZ4_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * @dictBuffer can be released after LZ4_CDict creation, since its content is copied within CDict. */ +LZ4FLIB_API LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize); +LZ4FLIB_API void LZ4F_freeCDict(LZ4F_CDict* CDict); + +/*! LZ4_compressFrame_usingCDict() : stable since v1.10 + * Compress an entire srcBuffer into a valid LZ4 frame using a digested Dictionary. + * @cctx must point to a context created by LZ4F_createCompressionContext(). + * If @cdict==NULL, compress without a dictionary. + * @dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * If this condition is not respected, function will fail (@return an errorCode). + * The LZ4F_preferences_t structure is optional : one may provide NULL as argument, + * but it's not recommended, as it's the only way to provide @dictID in the frame header. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + * Note: for larger inputs generating multiple independent blocks, + * this entry point uses the dictionary for each block. */ +LZ4FLIB_API size_t +LZ4F_compressFrame_usingCDict(LZ4F_cctx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr); + +/*! LZ4F_compressBegin_usingCDict() : stable since v1.10 + * Inits streaming dictionary compression, and writes the frame header into dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @prefsPtr is optional : one may provide NULL as argument, + * note however that it's the only way to insert a @dictID in the frame header. + * @cdict must outlive the compression session. + * @return : number of bytes written into dstBuffer for the header, + * or an error code, which can be tested using LZ4F_isError(). */ +LZ4FLIB_API size_t +LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* prefsPtr); + + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4F_H_09782039843 */ + +#if defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) +#define LZ4F_H_STATIC_09782039843 + +/* Note : + * The below declarations are not stable and may change in the future. + * They are therefore only safe to depend on + * when the caller is statically linked against the library. + * To access their declarations, define LZ4F_STATIC_LINKING_ONLY. + * + * By default, these symbols aren't published into shared/dynamic libraries. + * You can override this behavior and force them to be published + * by defining LZ4F_PUBLISH_STATIC_FUNCTIONS. + * Use at your own risk. + */ + +#if defined (__cplusplus) +extern "C" { +#endif + +#ifdef LZ4F_PUBLISH_STATIC_FUNCTIONS +# define LZ4FLIB_STATIC_API LZ4FLIB_API +#else +# define LZ4FLIB_STATIC_API +#endif + + +/* --- Error List --- */ +#define LZ4F_LIST_ERRORS(ITEM) \ + ITEM(OK_NoError) \ + ITEM(ERROR_GENERIC) \ + ITEM(ERROR_maxBlockSize_invalid) \ + ITEM(ERROR_blockMode_invalid) \ + ITEM(ERROR_parameter_invalid) \ + ITEM(ERROR_compressionLevel_invalid) \ + ITEM(ERROR_headerVersion_wrong) \ + ITEM(ERROR_blockChecksum_invalid) \ + ITEM(ERROR_reservedFlag_set) \ + ITEM(ERROR_allocation_failed) \ + ITEM(ERROR_srcSize_tooLarge) \ + ITEM(ERROR_dstMaxSize_tooSmall) \ + ITEM(ERROR_frameHeader_incomplete) \ + ITEM(ERROR_frameType_unknown) \ + ITEM(ERROR_frameSize_wrong) \ + ITEM(ERROR_srcPtr_wrong) \ + ITEM(ERROR_decompressionFailed) \ + ITEM(ERROR_headerChecksum_invalid) \ + ITEM(ERROR_contentChecksum_invalid) \ + ITEM(ERROR_frameDecoding_alreadyStarted) \ + ITEM(ERROR_compressionState_uninitialized) \ + ITEM(ERROR_parameter_null) \ + ITEM(ERROR_io_write) \ + ITEM(ERROR_io_read) \ + ITEM(ERROR_maxCode) + +#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, + +/* enum list is exposed, to handle specific errors */ +typedef enum { LZ4F_LIST_ERRORS(LZ4F_GENERATE_ENUM) + _LZ4F_dummy_error_enum_for_c89_never_used } LZ4F_errorCodes; + +LZ4FLIB_STATIC_API LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult); + +/********************************** + * Advanced compression operations + *********************************/ + +/*! LZ4F_getBlockSize() : + * @return, in scalar format (size_t), + * the maximum block size associated with @blockSizeID, + * or an error code (can be tested using LZ4F_isError()) if @blockSizeID is invalid. +**/ +LZ4FLIB_STATIC_API size_t LZ4F_getBlockSize(LZ4F_blockSizeID_t blockSizeID); + +/*! LZ4F_uncompressedUpdate() : + * LZ4F_uncompressedUpdate() can be called repetitively to add data stored as uncompressed blocks. + * Important rule: dstCapacity MUST be large enough to store the entire source buffer as + * no compression is done for this operation + * If this condition is not respected, LZ4F_uncompressedUpdate() will fail (result is an errorCode). + * After an error, the state is left in a UB state, and must be re-initialized or freed. + * If previously a compressed block was written, buffered data is flushed first, + * before appending uncompressed data is continued. + * This operation is only supported when LZ4F_blockIndependent is used. + * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. + * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_STATIC_API size_t +LZ4F_uncompressedUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); + +/********************************** + * Custom memory allocation + *********************************/ + +/*! Custom memory allocation : v1.9.4+ + * These prototypes make it possible to pass custom allocation/free functions. + * LZ4F_customMem is provided at state creation time, using LZ4F_create*_advanced() listed below. + * All allocation/free operations will be completed using these custom variants instead of regular ones. + */ +typedef void* (*LZ4F_AllocFunction) (void* opaqueState, size_t size); +typedef void* (*LZ4F_CallocFunction) (void* opaqueState, size_t size); +typedef void (*LZ4F_FreeFunction) (void* opaqueState, void* address); +typedef struct { + LZ4F_AllocFunction customAlloc; + LZ4F_CallocFunction customCalloc; /* optional; when not defined, uses customAlloc + memset */ + LZ4F_FreeFunction customFree; + void* opaqueState; +} LZ4F_CustomMem; +static +#ifdef __GNUC__ +__attribute__((__unused__)) +#endif +LZ4F_CustomMem const LZ4F_defaultCMem = { NULL, NULL, NULL, NULL }; /**< this constant defers to stdlib's functions */ + +LZ4FLIB_STATIC_API LZ4F_cctx* LZ4F_createCompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version); +LZ4FLIB_STATIC_API LZ4F_dctx* LZ4F_createDecompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version); +LZ4FLIB_STATIC_API LZ4F_CDict* LZ4F_createCDict_advanced(LZ4F_CustomMem customMem, const void* dictBuffer, size_t dictSize); + + +#if defined (__cplusplus) +} +#endif + +#endif /* defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) */ diff --git a/vendor/compress/lz4/src/lz4hc.h b/vendor/compress/lz4/src/lz4hc.h new file mode 100644 index 000000000..992bc8cdd --- /dev/null +++ b/vendor/compress/lz4/src/lz4hc.h @@ -0,0 +1,414 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Header File + Copyright (C) 2011-2020, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +#ifndef LZ4_HC_H_19834876238432 +#define LZ4_HC_H_19834876238432 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +/* note : lz4hc requires lz4.h/lz4.c for compilation */ +#include "lz4.h" /* stddef, LZ4LIB_API, LZ4_DEPRECATED */ + + +/* --- Useful constants --- */ +#define LZ4HC_CLEVEL_MIN 2 +#define LZ4HC_CLEVEL_DEFAULT 9 +#define LZ4HC_CLEVEL_OPT_MIN 10 +#define LZ4HC_CLEVEL_MAX 12 + + +/*-************************************ + * Block Compression + **************************************/ +/*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ +LZ4LIB_API int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel); + + +/* Note : + * Decompression functions are provided within "lz4.h" (BSD license) + */ + + +/*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ +LZ4LIB_API int LZ4_sizeofStateHC(void); +LZ4LIB_API int LZ4_compress_HC_extStateHC(void* stateHC, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); + + +/*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ +LZ4LIB_API int LZ4_compress_HC_destSize(void* stateHC, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize, + int compressionLevel); + + +/*-************************************ + * Streaming Compression + * Bufferless synchronous API + **************************************/ + typedef union LZ4_streamHC_u LZ4_streamHC_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_createStreamHC(void); +LZ4LIB_API int LZ4_freeStreamHC (LZ4_streamHC_t* streamHCPtr); + +/* + These functions compress data in successive blocks of any size, + using previous blocks as dictionary, to improve compression ratio. + One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. + There is an exception for ring buffers, which can be smaller than 64 KB. + Ring-buffer scenario is automatically detected and handled within LZ4_compress_HC_continue(). + + Before starting compression, state must be allocated and properly initialized. + LZ4_createStreamHC() does both, though compression level is set to LZ4HC_CLEVEL_DEFAULT. + + Selecting the compression level can be done with LZ4_resetStreamHC_fast() (starts a new stream) + or LZ4_setCompressionLevel() (anytime, between blocks in the same stream) (experimental). + LZ4_resetStreamHC_fast() only works on states which have been properly initialized at least once, + which is automatically the case when state is created using LZ4_createStreamHC(). + + After reset, a first "fictional block" can be designated as initial dictionary, + using LZ4_loadDictHC() (Optional). + Note: In order for LZ4_loadDictHC() to create the correct data structure, + it is essential to set the compression level _before_ loading the dictionary. + + Invoke LZ4_compress_HC_continue() to compress each successive block. + The number of blocks is unlimited. + Previous input blocks, including initial dictionary when present, + must remain accessible and unmodified during compression. + + It's allowed to update compression level anytime between blocks, + using LZ4_setCompressionLevel() (experimental). + + @dst buffer should be sized to handle worst case scenarios + (see LZ4_compressBound(), it ensures compression success). + In case of failure, the API does not guarantee recovery, + so the state _must_ be reset. + To ensure compression success + whenever @dst buffer size cannot be made >= LZ4_compressBound(), + consider using LZ4_compress_HC_continue_destSize(). + + Whenever previous input blocks can't be preserved unmodified in-place during compression of next blocks, + it's possible to copy the last blocks into a more stable memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer' (<= 64 KB) + + After completing a streaming compression, + it's possible to start a new stream of blocks, using the same LZ4_streamHC_t state, + just by resetting it, using LZ4_resetStreamHC_fast(). +*/ + +LZ4LIB_API void LZ4_resetStreamHC_fast(LZ4_streamHC_t* streamHCPtr, int compressionLevel); /* v1.9.0+ */ +LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); + +LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, + const char* src, char* dst, + int srcSize, int maxDstSize); + +/*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ +LZ4LIB_API int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize); + +LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); + + +/*! LZ4_attach_HC_dictionary() : stable since v1.10.0 + * This API allows for the efficient re-use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ +LZ4LIB_API void +LZ4_attach_HC_dictionary(LZ4_streamHC_t* working_stream, + const LZ4_streamHC_t* dictionary_stream); + + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-****************************************************************** + * PRIVATE DEFINITIONS : + * Do not use these definitions directly. + * They are merely exposed to allow static allocation of `LZ4_streamHC_t`. + * Declare an `LZ4_streamHC_t` directly, rather than any type below. + * Even then, only do so in the context of static linking, as definitions may change between versions. + ********************************************************************/ + +#define LZ4HC_DICTIONARY_LOGSIZE 16 +#define LZ4HC_MAXD (1<= LZ4HC_CLEVEL_OPT_MIN. + */ +LZ4LIB_STATIC_API void LZ4_favorDecompressionSpeed( + LZ4_streamHC_t* LZ4_streamHCPtr, int favor); + +/*! LZ4_resetStreamHC_fast() : v1.9.0+ + * When an LZ4_streamHC_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStreamHC()). + * + * LZ4_streamHCs are guaranteed to be in a valid state when: + * - returned from LZ4_createStreamHC() + * - reset by LZ4_resetStreamHC() + * - memset(stream, 0, sizeof(LZ4_streamHC_t)) + * - the stream was in a valid state and was reset by LZ4_resetStreamHC_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_HC_extStateHC()) and that + * returned success + * + * Note: + * A stream that was last used in a compression call that returned an error + * may be passed to this function. However, it will be fully reset, which will + * clear any existing history and settings from the context. + */ +LZ4LIB_STATIC_API void LZ4_resetStreamHC_fast( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_compress_HC_extStateHC_fastReset() : + * A variant of LZ4_compress_HC_extStateHC(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStreamHC_fast() for a definition of + * "correctly initialized"). From a high level, the difference is that this + * function initializes the provided state with a call to + * LZ4_resetStreamHC_fast() while LZ4_compress_HC_extStateHC() starts with a + * call to LZ4_resetStreamHC(). + */ +LZ4LIB_STATIC_API int LZ4_compress_HC_extStateHC_fastReset ( + void* state, + const char* src, char* dst, + int srcSize, int dstCapacity, + int compressionLevel); + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4_HC_SLO_098092834 */ +#endif /* LZ4_HC_STATIC_LINKING_ONLY */ diff --git a/vendor/directx/dxgi/dxgi.odin b/vendor/directx/dxgi/dxgi.odin index 235e07633..5b6137cee 100644 --- a/vendor/directx/dxgi/dxgi.odin +++ b/vendor/directx/dxgi/dxgi.odin @@ -29,14 +29,9 @@ SIZE :: win32.SIZE WCHAR :: win32.WCHAR DWORD :: win32.DWORD -IUnknown :: struct { - using _iunknown_vtable: ^IUnknown_VTable, -} -IUnknown_VTable :: struct { - QueryInterface: proc "system" (this: ^IUnknown, riid: ^IID, ppvObject: ^rawptr) -> HRESULT, - AddRef: proc "system" (this: ^IUnknown) -> ULONG, - Release: proc "system" (this: ^IUnknown) -> ULONG, -} +IUnknown :: win32.IUnknown +IUnknown_VTable :: win32.IUnknown_VTable +LPUNKNOWN :: win32.LPUNKNOWN @(default_calling_convention="system") foreign dxgi { diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 9b556a04a..2c692db06 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -1,14 +1,14 @@ -//+build windows, linux, darwin //+vet !using-param package fontstash import "base:runtime" + import "core:log" -import "core:os" import "core:mem" import "core:math" import "core:strings" import "core:slice" + import stbtt "vendor:stb/truetype" // This is a port from Fontstash into odin - specialized for nanovg @@ -321,20 +321,6 @@ __AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) -> bool { return true } -AddFontPath :: proc( - ctx: ^FontContext, - name: string, - path: string, -) -> int { - data, ok := os.read_entire_file(path) - - if !ok { - log.panicf("FONT: failed to read font at %s", path) - } - - return AddFontMem(ctx, name, data, true) -} - // push a font to the font stack // optionally init with ascii characters at a wanted size AddFontMem :: proc( @@ -1192,4 +1178,4 @@ EndState :: proc(using ctx: ^FontContext) { } __dirtyRectReset(ctx) } -} \ No newline at end of file +} diff --git a/vendor/fontstash/fontstash_os.odin b/vendor/fontstash/fontstash_os.odin new file mode 100644 index 000000000..6182573bd --- /dev/null +++ b/vendor/fontstash/fontstash_os.odin @@ -0,0 +1,20 @@ +//+build !js +package fontstash + +import "core:log" +import "core:os" + +AddFontPath :: proc( + ctx: ^FontContext, + name: string, + path: string, +) -> int { + data, ok := os.read_entire_file(path) + + if !ok { + log.panicf("FONT: failed to read font at %s", path) + } + + return AddFontMem(ctx, name, data, true) +} + diff --git a/vendor/fontstash/fontstash_other.odin b/vendor/fontstash/fontstash_other.odin new file mode 100644 index 000000000..1c2ca3f28 --- /dev/null +++ b/vendor/fontstash/fontstash_other.odin @@ -0,0 +1,10 @@ +//+build js +package fontstash + +AddFontPath :: proc( + ctx: ^FontContext, + name: string, + path: string, +) -> int { + panic("fontstash.AddFontPath is unsupported on the JS target") +} diff --git a/vendor/glfw/LICENSE.txt b/vendor/glfw/LICENSE.txt new file mode 100644 index 000000000..b8c096845 --- /dev/null +++ b/vendor/glfw/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2002-2006 Marcus Geelnard + +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. diff --git a/vendor/glfw/bindings/bindings.odin b/vendor/glfw/bindings/bindings.odin index 164a8ea2d..81569f177 100644 --- a/vendor/glfw/bindings/bindings.odin +++ b/vendor/glfw/bindings/bindings.odin @@ -197,7 +197,12 @@ foreign glfw { SetErrorCallback :: proc(cbfun: ErrorProc) -> ErrorProc --- + // Functions added in 3.4, Linux links against system glfw so we define these as weak to be able + // to check at runtime if they are available. + + @(linkage="weak") GetPlatform :: proc() -> c.int --- + @(linkage="weak") PlatformSupported :: proc(platform: c.int) -> b32 --- } diff --git a/vendor/glfw/native_linux.odin b/vendor/glfw/native_linux.odin index 6833d2893..acae8a27e 100644 --- a/vendor/glfw/native_linux.odin +++ b/vendor/glfw/native_linux.odin @@ -13,7 +13,13 @@ foreign { SetX11SelectionString :: proc(string: cstring) --- GetX11SelectionString :: proc() -> cstring --- + // Functions added in 3.4, Linux links against system glfw so we define these as weak to be able + // to check at runtime if they are available. + + @(linkage="weak") GetWaylandDisplay :: proc() -> rawptr /* struct wl_display* */ --- + @(linkage="weak") GetWaylandWindow :: proc(window: WindowHandle) -> rawptr /* struct wl_surface* */ --- + @(linkage="weak") GetWaylandMonitor :: proc(monitor: MonitorHandle) -> rawptr /* struct wl_output* */ --- } diff --git a/vendor/lua/5.1/lua.odin b/vendor/lua/5.1/lua.odin index b53c61bb3..5b7482931 100644 --- a/vendor/lua/5.1/lua.odin +++ b/vendor/lua/5.1/lua.odin @@ -16,7 +16,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.so" } else { - foreign import lib "system:liblua.so.5.1" + foreign import lib "system:lua5.1" } } else { when ODIN_OS == .Windows { @@ -24,7 +24,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.a" } else { - foreign import lib "system:liblua5.1.a" + foreign import lib "system:lua5.1" } } diff --git a/vendor/lua/5.2/lua.odin b/vendor/lua/5.2/lua.odin index 5474da95d..d5d8ec253 100644 --- a/vendor/lua/5.2/lua.odin +++ b/vendor/lua/5.2/lua.odin @@ -16,7 +16,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.so" } else { - foreign import lib "system:liblua.so.5.2" + foreign import lib "system:lua5.2" } } else { when ODIN_OS == .Windows { @@ -24,7 +24,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.a" } else { - foreign import lib "system:liblua52.a" + foreign import lib "system:lua5.2" } } diff --git a/vendor/lua/5.3/lua.odin b/vendor/lua/5.3/lua.odin index e0975e5f8..47215a327 100644 --- a/vendor/lua/5.3/lua.odin +++ b/vendor/lua/5.3/lua.odin @@ -16,7 +16,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.so" } else { - foreign import lib "system:liblua.so.5.3" + foreign import lib "system:lua5.3" } } else { when ODIN_OS == .Windows { @@ -24,7 +24,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.a" } else { - foreign import lib "system:liblua53.a" + foreign import lib "system:lua5.3" } } diff --git a/vendor/lua/5.4/lua.odin b/vendor/lua/5.4/lua.odin index 80f7ead3a..9be8fea55 100644 --- a/vendor/lua/5.4/lua.odin +++ b/vendor/lua/5.4/lua.odin @@ -16,15 +16,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.so" } else { - // Note(bumbread): My linux system has a few aliases for this shared object - // lublua5.4.so, liblua.so, lublua.so.5.4, liblua.so.5.4.6. I don't know - // who enforces these numbers (probably ld?), and if it can be done in a - // unix-generic way, but in any way I think the most sane thing to do is to - // keep it close to what linux does and if it breaks, just special case those - // operating systems. - // Also there was no alias for liblua54.so, that seems to suggest that way - // of specifying it isn't portable - foreign import lib "system:liblua.so.5.4" + foreign import lib "system:lua5.4" } } else { when ODIN_OS == .Windows { @@ -32,7 +24,7 @@ when LUA_SHARED { } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.a" } else { - foreign import lib "system:liblua54.a" + foreign import lib "system:lua5.4" } } diff --git a/vendor/lua/README.md b/vendor/lua/README.md index 8f4b0f5a5..4bc7804bb 100644 --- a/vendor/lua/README.md +++ b/vendor/lua/README.md @@ -1,12 +1,50 @@ # Lua in Odin -```odin -import lua "vendor:lua/5.4" // or whatever version you want -``` - Lua packages * `vendor:lua/5.1` (version 5.1.5) * `vendor:lua/5.2` (version 5.2.4) * `vendor:lua/5.3` (version 5.3.6) -* `vendor:lua/5.4` (version 5.4.2) \ No newline at end of file +* `vendor:lua/5.4` (version 5.4.2) + +With custom context-based allocator: + +```odin +package lua_example_with_context + +import "core:fmt" +import lua "vendor:lua/5.4" // or whatever version you want +import "core:c" +import "base:runtime" + +state: ^lua.State + +lua_allocator :: proc "c" (ud: rawptr, ptr: rawptr, osize, nsize: c.size_t) -> (buf: rawptr) { + old_size := int(osize) + new_size := int(nsize) + context = (^runtime.Context)(ud)^ + + if ptr == nil { + data, err := runtime.mem_alloc(new_size) + return raw_data(data) if err == .None else nil + } else { + if nsize > 0 { + data, err := runtime.mem_resize(ptr, old_size, new_size) + return raw_data(data) if err == .None else nil + } else { + runtime.mem_free(ptr) + return + } + } +} + +main :: proc() { + _context := context + state = lua.newstate(lua_allocator, &_context) + defer lua.close(state) + + lua.L_dostring(state, "return 'somestring'") + str := lua.tostring(state, -1) + fmt.println(str) +} +``` \ No newline at end of file diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index 20b5640f6..c367f125c 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -28,13 +28,6 @@ when ODIN_OS == .Windows { RAYGUI_VERSION :: "4.0" -// Style property -GuiStyleProp :: struct { - controlId: u16, - propertyId: u16, - propertyValue: c.int, -} - // Gui control state GuiState :: enum c.int { STATE_NORMAL = 0, @@ -236,8 +229,8 @@ foreign lib { // Style set/get functions - GuiSetStyle :: proc(control: GuiControl, property: GuiStyleProp, value: c.int) --- // Set one style property - GuiGetStyle :: proc(control: GuiControl, property: GuiStyleProp) -> c.int --- // Get one style property + GuiSetStyle :: proc(control: GuiControl, property: GuiControlProperty, value: c.int) --- // Set one style property + GuiGetStyle :: proc(control: GuiControl, property: GuiControlProperty) -> c.int --- // Get one style property // Styles loading functions diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 42199f906..c7cb41019 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1015,8 +1015,8 @@ foreign lib { SetRandomSeed :: proc(seed: c.uint) --- // Set the seed for the random number generator GetRandomValue :: proc(min, max: c.int) -> c.int --- // Get a random value between min and max (both included) - LoadRandomSequence :: proc(count : c.uint, min, max: c.int) --- // Load random values sequence, no values repeated - UnloadRandomSequence :: proc(sequence : ^c.int) --- // Unload random values sequence + LoadRandomSequence :: proc(count: c.uint, min, max: c.int) --- // Load random values sequence, no values repeated + UnloadRandomSequence :: proc(sequence: ^c.int) --- // Unload random values sequence // Misc. functions TakeScreenshot :: proc(fileName: cstring) --- // Takes a screenshot of current screen (filename extension defines format) @@ -1424,9 +1424,9 @@ foreign lib { LoadUTF8 :: proc(codepoints: [^]rune, length: c.int) -> [^]byte --- // Load UTF-8 text encoded from codepoints array UnloadUTF8 :: proc(text: [^]byte) --- // Unload UTF-8 text encoded from codepoints array - LoadCodepoints :: proc(text: rawptr, count: ^c.int) -> [^]rune --- // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter + LoadCodepoints :: proc(text: cstring, count: ^c.int) -> [^]rune --- // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter UnloadCodepoints :: proc(codepoints: [^]rune) --- // Unload codepoints data from memory - GetCodepointCount :: proc(text : cstring) -> c.int --- // Get total number of codepoints in a UTF-8 encoded string + GetCodepointCount :: proc(text: cstring) -> c.int --- // Get total number of codepoints in a UTF-8 encoded string GetCodepoint :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure GetCodepointNext :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure GetCodepointPrevious :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure @@ -1667,7 +1667,7 @@ IsGestureDetected :: proc "c" (gesture: Gesture) -> bool { // Text formatting with variables (sprintf style) -TextFormat :: proc(text: cstring, args: ..any) -> cstring { +TextFormat :: proc(text: cstring, args: ..any) -> cstring { @static buffers: [MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH]byte @static index: u32 @@ -1683,7 +1683,7 @@ TextFormat :: proc(text: cstring, args: ..any) -> cstring { } // Text formatting with variables (sprintf style) and allocates (must be freed with 'MemFree') -TextFormatAlloc :: proc(text: cstring, args: ..any) -> cstring { +TextFormatAlloc :: proc(text: cstring, args: ..any) -> cstring { str := fmt.tprintf(string(text), ..args) return strings.clone_to_cstring(str, MemAllocator()) } diff --git a/vendor/sdl2/sdl_pixels.odin b/vendor/sdl2/sdl_pixels.odin index 9eccbc6ab..195f2920f 100644 --- a/vendor/sdl2/sdl_pixels.odin +++ b/vendor/sdl2/sdl_pixels.odin @@ -22,7 +22,7 @@ PIXELTYPE_ARRAYU8 :: 7 PIXELTYPE_ARRAYU16 :: 8 PIXELTYPE_ARRAYU32 :: 9 PIXELTYPE_ARRAYF16 :: 10 -PIXELTYPE_ARRAYF3 :: 11 +PIXELTYPE_ARRAYF32 :: 11 BITMAPORDER_NONE :: 0 BITMAPORDER_4321 :: 1 @@ -47,7 +47,7 @@ ARRAYORDER_RGBA :: 2 ARRAYORDER_ARGB :: 3 ARRAYORDER_BGR :: 4 ARRAYORDER_BGRA :: 5 -ARRAYORDER_ABG :: 6 +ARRAYORDER_ABGR :: 6 PACKEDLAYOUT_NONE :: 0 PACKEDLAYOUT_332 :: 1 diff --git a/vendor/stb/lib/stb_truetype_wasm.o b/vendor/stb/lib/stb_truetype_wasm.o new file mode 100644 index 000000000..15c4fa0d5 Binary files /dev/null and b/vendor/stb/lib/stb_truetype_wasm.o differ diff --git a/vendor/stb/src/Makefile b/vendor/stb/src/Makefile index 1027cf8d2..6123a95fa 100644 --- a/vendor/stb/src/Makefile +++ b/vendor/stb/src/Makefile @@ -6,6 +6,10 @@ else all: unix endif +wasm: + mkdir -p ../lib + clang -c -Os --target=wasm32 -nostdlib stb_truetype_wasm.c -o ../lib/stb_truetype_wasm.o + unix: mkdir -p ../lib $(CC) -c -O2 -Os -fPIC stb_image.c stb_image_write.c stb_image_resize.c stb_truetype.c stb_rect_pack.c stb_vorbis.c diff --git a/vendor/stb/src/stb_truetype_wasm.c b/vendor/stb/src/stb_truetype_wasm.c new file mode 100644 index 000000000..e0b1fdc77 --- /dev/null +++ b/vendor/stb/src/stb_truetype_wasm.c @@ -0,0 +1,46 @@ +#include + +void *stbtt_malloc(size_t size); +void stbtt_free(void *ptr); + +void stbtt_qsort(void* base, size_t num, size_t size, int (*compare)(const void*, const void*)); + +double stbtt_floor(double x); +double stbtt_ceil(double x); +double stbtt_sqrt(double x); +double stbtt_pow(double x, double y); +double stbtt_fmod(double x, double y); +double stbtt_cos(double x); +double stbtt_acos(double x); +double stbtt_fabs(double x); + +unsigned long stbtt_strlen(const char *str); + +void *memcpy(void *dst, const void *src, size_t count); +void *memset(void *dst, int x, size_t count); + +#define STBRP_SORT stbtt_qsort +#define STBRP_ASSERT(condition) ((void)0) + +#define STBTT_malloc(x,u) ((void)(u),stbtt_malloc(x)) +#define STBTT_free(x,u) ((void)(u),stbtt_free(x)) + +#define STBTT_assert(condition) ((void)0) + +#define STBTT_ifloor(x) ((int) stbtt_floor(x)) +#define STBTT_iceil(x) ((int) stbtt_ceil(x)) +#define STBTT_sqrt(x) stbtt_sqrt(x) +#define STBTT_pow(x,y) stbtt_pow(x,y) +#define STBTT_fmod(x,y) stbtt_fmod(x,y) +#define STBTT_cos(x) stbtt_cos(x) +#define STBTT_acos(x) stbtt_acos(x) +#define STBTT_fabs(x) stbtt_fabs(x) +#define STBTT_strlen(x) stbtt_strlen(x) +#define STBTT_memcpy memcpy +#define STBTT_memset memset + +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index 876138c3a..e6defff5f 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -17,6 +17,8 @@ when LIB != "" { } foreign import stbtt { LIB } +} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + foreign import stbtt "../lib/stb_truetype_wasm.o" } else { foreign import stbtt "system:stb_truetype" } @@ -566,7 +568,7 @@ foreign stbtt { // some of the values for the IDs are below; for more see the truetype spec: // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html // http://www.microsoft.com/typography/otspec/name.htm - GetFontNameString :: proc(font: ^fontinfo, length: c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring --- + GetFontNameString :: proc(font: ^fontinfo, length: ^c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring --- } diff --git a/vendor/stb/truetype/stb_truetype_wasm.odin b/vendor/stb/truetype/stb_truetype_wasm.odin new file mode 100644 index 000000000..ff1d4fac5 --- /dev/null +++ b/vendor/stb/truetype/stb_truetype_wasm.odin @@ -0,0 +1,88 @@ +//+build wasm32, wasm64p32 +package stb_truetype + +import "base:builtin" +import "base:intrinsics" +import "base:runtime" + +import "core:c" +import "core:math" +import "core:slice" +import "core:sort" + +@(require, linkage="strong", link_name="stbtt_malloc") +malloc :: proc "c" (size: uint) -> rawptr { + context = runtime.default_context() + ptr, _ := runtime.mem_alloc_non_zeroed(int(size)) + return raw_data(ptr) +} + +@(require, linkage="strong", link_name="stbtt_free") +free :: proc "c" (ptr: rawptr) { + context = runtime.default_context() + builtin.free(ptr) +} + +@(require, linkage="strong", link_name="stbtt_qsort") +qsort :: proc "c" (base: rawptr, num: uint, size: uint, cmp: proc "c" (a, b: rawptr) -> i32) { + context = runtime.default_context() + + Inputs :: struct { + base: rawptr, + num: uint, + size: uint, + cmp: proc "c" (a, b: rawptr) -> i32, + } + + sort.sort({ + collection = &Inputs{base, num, size, cmp}, + len = proc(it: sort.Interface) -> int { + inputs := (^Inputs)(it.collection) + return int(inputs.num) + }, + less = proc(it: sort.Interface, i, j: int) -> bool { + inputs := (^Inputs)(it.collection) + a := rawptr(uintptr(inputs.base) + (uintptr(i) * uintptr(inputs.size))) + b := rawptr(uintptr(inputs.base) + (uintptr(j) * uintptr(inputs.size))) + return inputs.cmp(a, b) < 0 + }, + swap = proc(it: sort.Interface, i, j: int) { + inputs := (^Inputs)(it.collection) + + a := rawptr(uintptr(inputs.base) + (uintptr(i) * uintptr(inputs.size))) + b := rawptr(uintptr(inputs.base) + (uintptr(j) * uintptr(inputs.size))) + + slice.ptr_swap_non_overlapping(a, b, int(inputs.size)) + }, + }) +} + +@(require, linkage="strong", link_name="stbtt_floor") +floor :: proc "c" (x: f64) -> f64 { return math.floor(x) } +@(require, linkage="strong", link_name="stbtt_ceil") +ceil :: proc "c" (x: f64) -> f64 { return math.ceil(x) } +@(require, linkage="strong", link_name="stbtt_sqrt") +sqrt :: proc "c" (x: f64) -> f64 { return math.sqrt(x) } +@(require, linkage="strong", link_name="stbtt_pow") +pow :: proc "c" (x, y: f64) -> f64 { return math.pow(x, y) } + +@(require, linkage="strong", link_name="stbtt_fmod") +fmod :: proc "c" (x, y: f64) -> f64 { + context = runtime.default_context() + // NOTE: only called in the `stbtt_GetGlyphSDF` code path. + panic("`math.round` is broken on 32 bit targets, see #3856") +} + +@(require, linkage="strong", link_name="stbtt_cos") +cos :: proc "c" (x: f64) -> f64 { return math.cos(x) } +@(require, linkage="strong", link_name="stbtt_acos") +acos :: proc "c" (x: f64) -> f64 { return math.acos(x) } +@(require, linkage="strong", link_name="stbtt_fabs") +fabs :: proc "c" (x: f64) -> f64 { return math.abs(x) } + +@(require, linkage="strong", link_name="stbtt_strlen") +strlen :: proc "c" (str: cstring) -> c.ulong { return c.ulong(len(str)) } + +// NOTE: defined in runtime. +// void *memcpy(void *dst, const void *src, size_t count); +// void *memset(void *dst, int x, size_t count); diff --git a/vendor/wasm/WebGL/webgl.odin b/vendor/wasm/WebGL/webgl.odin index 0ecfa8644..389e729de 100644 --- a/vendor/wasm/WebGL/webgl.odin +++ b/vendor/wasm/WebGL/webgl.odin @@ -247,7 +247,7 @@ BufferDataSlice :: proc "contextless" (target: Enum, slice: $S/[]$E, usage: Enum BufferData(target, len(slice)*size_of(E), raw_data(slice), usage) } BufferSubDataSlice :: proc "contextless" (target: Enum, offset: uintptr, slice: $S/[]$E) { - BufferSubData(target, offset, len(slice)*size_of(E), raw_data(slice), usage) + BufferSubData(target, offset, len(slice)*size_of(E), raw_data(slice)) } CompressedTexImage2DSlice :: proc "contextless" (target: Enum, level: i32, internalformat: Enum, width, height: i32, border: i32, slice: $S/[]$E) { diff --git a/vendor/wasm/js/events.odin b/vendor/wasm/js/events.odin index 2d3f01ceb..4e786e2be 100644 --- a/vendor/wasm/js/events.odin +++ b/vendor/wasm/js/events.odin @@ -33,6 +33,7 @@ Event_Kind :: enum u32 { Submit, Blur, Change, + HashChange, Select, Animation_Start, @@ -112,6 +113,7 @@ event_kind_string := [Event_Kind]string{ .Submit = "submit", .Blur = "blur", .Change = "change", + .HashChange = "hashchange", .Select = "select", .Animation_Start = "animationstart", diff --git a/vendor/wgpu/README.md b/vendor/wgpu/README.md index 3561642f4..8b2c95b5e 100644 --- a/vendor/wgpu/README.md +++ b/vendor/wgpu/README.md @@ -37,12 +37,25 @@ The bindings work on both `-target:js_wasm32` and `-target:js_wasm64p32`. ## GLFW Glue There is an inner package `glfwglue` that can be used to glue together WGPU and GLFW. -It exports one procedure `GetSurface(wgpu.Instance, glfw.WindowHandle) -> glfw.Surface`. +It exports one procedure `GetSurface(wgpu.Instance, glfw.WindowHandle) -> wgpu.Surface`. The procedure will call the needed target specific procedures and return a surface configured for the given window. -To support Wayland on Linux, you need to have GLFW compiled to support it, and use -`-define:WGPU_GFLW_GLUE_SUPPORT_WAYLAND=true` to enable the package to check for Wayland. - Do note that wgpu does not require GLFW, you can use native windows or another windowing library too. For that you can take inspiration from `glfwglue` on glueing them together. + +## SDL2 Glue + +There is an inner package `sdl2glue` that can be used to glue together WGPU and SDL2. +It exports one procedure `GetSurface(wgpu.Instance, ^sdl2.Window) -> wgpu.Surface`. +The procedure will call the needed target specific procedures and return a surface configured +for the given window. + +### Wayland + +GLFW supports Wayland from version 3.4 onwards and only if it is compiled with `-DGLFW_EXPOSE_NATIVE_WAYLAND`. + +Odin links against your system's glfw library (probably installed through a package manager). +If that version is lower than 3.4 or hasn't been compiled with the previously mentioned define, +you will have to compile glfw from source yourself and adjust the `foreign import` declarations in `vendor:glfw/bindings` to +point to it. diff --git a/vendor/wgpu/example/Makefile b/vendor/wgpu/examples/glfw/Makefile similarity index 78% rename from vendor/wgpu/example/Makefile rename to vendor/wgpu/examples/glfw/Makefile index f19997881..fdecdbb91 100644 --- a/vendor/wgpu/example/Makefile +++ b/vendor/wgpu/examples/glfw/Makefile @@ -8,10 +8,10 @@ PAGE_SIZE := 65536 INITIAL_MEMORY_BYTES := $(shell expr $(INITIAL_MEMORY_PAGES) \* $(PAGE_SIZE)) MAX_MEMORY_BYTES := $(shell expr $(MAX_MEMORY_PAGES) \* $(PAGE_SIZE)) -web/triangle.wasm: $(FILES) ../wgpu.js ../../wasm/js/runtime.js +web/triangle.wasm: $(FILES) ../../wgpu.js ../../../wasm/js/runtime.js odin build . \ -target:js_wasm32 -out:web/triangle.wasm -o:size \ -extra-linker-flags:"--export-table --import-memory --initial-memory=$(INITIAL_MEMORY_BYTES) --max-memory=$(MAX_MEMORY_BYTES)" - cp ../wgpu.js web/wgpu.js - cp ../../wasm/js/runtime.js web/runtime.js + cp ../../wgpu.js web/wgpu.js + cp ../../../wasm/js/runtime.js web/runtime.js diff --git a/vendor/wgpu/example/build.bat b/vendor/wgpu/examples/glfw/build.bat similarity index 84% rename from vendor/wgpu/example/build.bat rename to vendor/wgpu/examples/glfw/build.bat index cd3ca63ba..61afcbe66 100644 --- a/vendor/wgpu/example/build.bat +++ b/vendor/wgpu/examples/glfw/build.bat @@ -8,5 +8,5 @@ set /a MAX_MEMORY_BYTES=%MAX_MEMORY_PAGES% * %PAGE_SIZE% call odin.exe build . -target:js_wasm32 -out:web/triangle.wasm -o:size -extra-linker-flags:"--export-table --import-memory --initial-memory=%INITIAL_MEMORY_BYTES% --max-memory=%MAX_MEMORY_BYTES%" -copy "..\wgpu.js" "web\wgpu.js" -copy "..\..\wasm\js\runtime.js" "web\runtime.js" \ No newline at end of file +copy "..\..\wgpu.js" "web\wgpu.js" +copy "..\..\..\wasm\js\runtime.js" "web\runtime.js" \ No newline at end of file diff --git a/vendor/wgpu/example/main.odin b/vendor/wgpu/examples/glfw/main.odin similarity index 100% rename from vendor/wgpu/example/main.odin rename to vendor/wgpu/examples/glfw/main.odin diff --git a/vendor/wgpu/example/os_glfw.odin b/vendor/wgpu/examples/glfw/os_glfw.odin similarity index 100% rename from vendor/wgpu/example/os_glfw.odin rename to vendor/wgpu/examples/glfw/os_glfw.odin diff --git a/vendor/wgpu/example/os_js.odin b/vendor/wgpu/examples/glfw/os_js.odin similarity index 100% rename from vendor/wgpu/example/os_js.odin rename to vendor/wgpu/examples/glfw/os_js.odin diff --git a/vendor/wgpu/example/web/index.html b/vendor/wgpu/examples/glfw/web/index.html similarity index 100% rename from vendor/wgpu/example/web/index.html rename to vendor/wgpu/examples/glfw/web/index.html diff --git a/vendor/wgpu/examples/sdl2/Makefile b/vendor/wgpu/examples/sdl2/Makefile new file mode 100644 index 000000000..fdecdbb91 --- /dev/null +++ b/vendor/wgpu/examples/sdl2/Makefile @@ -0,0 +1,17 @@ +FILES := $(wildcard *) + +# NOTE: changing this requires changing the same values in the `web/index.html`. +INITIAL_MEMORY_PAGES := 2000 +MAX_MEMORY_PAGES := 65536 + +PAGE_SIZE := 65536 +INITIAL_MEMORY_BYTES := $(shell expr $(INITIAL_MEMORY_PAGES) \* $(PAGE_SIZE)) +MAX_MEMORY_BYTES := $(shell expr $(MAX_MEMORY_PAGES) \* $(PAGE_SIZE)) + +web/triangle.wasm: $(FILES) ../../wgpu.js ../../../wasm/js/runtime.js + odin build . \ + -target:js_wasm32 -out:web/triangle.wasm -o:size \ + -extra-linker-flags:"--export-table --import-memory --initial-memory=$(INITIAL_MEMORY_BYTES) --max-memory=$(MAX_MEMORY_BYTES)" + + cp ../../wgpu.js web/wgpu.js + cp ../../../wasm/js/runtime.js web/runtime.js diff --git a/vendor/wgpu/examples/sdl2/build.bat b/vendor/wgpu/examples/sdl2/build.bat new file mode 100644 index 000000000..61afcbe66 --- /dev/null +++ b/vendor/wgpu/examples/sdl2/build.bat @@ -0,0 +1,12 @@ +REM NOTE: changing this requires changing the same values in the `web/index.html`. +set INITIAL_MEMORY_PAGES=2000 +set MAX_MEMORY_PAGES=65536 + +set PAGE_SIZE=65536 +set /a INITIAL_MEMORY_BYTES=%INITIAL_MEMORY_PAGES% * %PAGE_SIZE% +set /a MAX_MEMORY_BYTES=%MAX_MEMORY_PAGES% * %PAGE_SIZE% + +call odin.exe build . -target:js_wasm32 -out:web/triangle.wasm -o:size -extra-linker-flags:"--export-table --import-memory --initial-memory=%INITIAL_MEMORY_BYTES% --max-memory=%MAX_MEMORY_BYTES%" + +copy "..\..\wgpu.js" "web\wgpu.js" +copy "..\..\..\wasm\js\runtime.js" "web\runtime.js" \ No newline at end of file diff --git a/vendor/wgpu/examples/sdl2/main.odin b/vendor/wgpu/examples/sdl2/main.odin new file mode 100644 index 000000000..3d79346d0 --- /dev/null +++ b/vendor/wgpu/examples/sdl2/main.odin @@ -0,0 +1,187 @@ +package vendor_wgpu_example_triangle + +import "base:runtime" + +import "core:fmt" + +import "vendor:wgpu" + +State :: struct { + ctx: runtime.Context, + os: OS, + + instance: wgpu.Instance, + surface: wgpu.Surface, + adapter: wgpu.Adapter, + device: wgpu.Device, + config: wgpu.SurfaceConfiguration, + queue: wgpu.Queue, + module: wgpu.ShaderModule, + pipeline_layout: wgpu.PipelineLayout, + pipeline: wgpu.RenderPipeline, +} + +@(private="file") +state: State + +main :: proc() { + state.ctx = context + + os_init(&state.os) + + state.instance = wgpu.CreateInstance(nil) + if state.instance == nil { + panic("WebGPU is not supported") + } + state.surface = os_get_surface(&state.os, state.instance) + + wgpu.InstanceRequestAdapter(state.instance, &{ compatibleSurface = state.surface }, on_adapter, nil) + + on_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: cstring, userdata: rawptr) { + context = state.ctx + if status != .Success || adapter == nil { + fmt.panicf("request adapter failure: [%v] %s", status, message) + } + state.adapter = adapter + wgpu.AdapterRequestDevice(adapter, nil, on_device) + } + + on_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: cstring, userdata: rawptr) { + context = state.ctx + if status != .Success || device == nil { + fmt.panicf("request device failure: [%v] %s", status, message) + } + state.device = device + + width, height := os_get_render_bounds(&state.os) + + state.config = wgpu.SurfaceConfiguration { + device = state.device, + usage = { .RenderAttachment }, + format = .BGRA8Unorm, + width = width, + height = height, + presentMode = .Fifo, + alphaMode = .Opaque, + } + wgpu.SurfaceConfigure(state.surface, &state.config) + + state.queue = wgpu.DeviceGetQueue(state.device) + + shader :: ` + @vertex + fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + }` + + state.module = wgpu.DeviceCreateShaderModule(state.device, &{ + nextInChain = &wgpu.ShaderModuleWGSLDescriptor{ + sType = .ShaderModuleWGSLDescriptor, + code = shader, + }, + }) + + state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{}) + state.pipeline = wgpu.DeviceCreateRenderPipeline(state.device, &{ + layout = state.pipeline_layout, + vertex = { + module = state.module, + entryPoint = "vs_main", + }, + fragment = &{ + module = state.module, + entryPoint = "fs_main", + targetCount = 1, + targets = &wgpu.ColorTargetState{ + format = .BGRA8Unorm, + writeMask = wgpu.ColorWriteMaskFlags_All, + }, + }, + primitive = { + topology = .TriangleList, + + }, + multisample = { + count = 1, + mask = 0xFFFFFFFF, + }, + }) + + os_run(&state.os) + } +} + +resize :: proc "c" () { + context = state.ctx + + state.config.width, state.config.height = os_get_render_bounds(&state.os) + wgpu.SurfaceConfigure(state.surface, &state.config) +} + +frame :: proc "c" (dt: f32) { + context = state.ctx + + surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface) + switch surface_texture.status { + case .Success: + // All good, could check for `surface_texture.suboptimal` here. + case .Timeout, .Outdated, .Lost: + // Skip this frame, and re-configure surface. + if surface_texture.texture != nil { + wgpu.TextureRelease(surface_texture.texture) + } + resize() + return + case .OutOfMemory, .DeviceLost: + // Fatal error + fmt.panicf("[triangle] get_current_texture status=%v", surface_texture.status) + } + defer wgpu.TextureRelease(surface_texture.texture) + + frame := wgpu.TextureCreateView(surface_texture.texture, nil) + defer wgpu.TextureViewRelease(frame) + + command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil) + defer wgpu.CommandEncoderRelease(command_encoder) + + render_pass_encoder := wgpu.CommandEncoderBeginRenderPass( + command_encoder, &{ + colorAttachmentCount = 1, + colorAttachments = &wgpu.RenderPassColorAttachment{ + view = frame, + loadOp = .Clear, + storeOp = .Store, + clearValue = { r = 0, g = 1, b = 0, a = 1 }, + }, + }, + ) + defer wgpu.RenderPassEncoderRelease(render_pass_encoder) + + wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline) + wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0) + wgpu.RenderPassEncoderEnd(render_pass_encoder) + + command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil) + defer wgpu.CommandBufferRelease(command_buffer) + + wgpu.QueueSubmit(state.queue, { command_buffer }) + wgpu.SurfacePresent(state.surface) +} + +finish :: proc() { + wgpu.RenderPipelineRelease(state.pipeline) + wgpu.PipelineLayoutRelease(state.pipeline_layout) + wgpu.ShaderModuleRelease(state.module) + wgpu.QueueRelease(state.queue) + wgpu.DeviceRelease(state.device) + wgpu.AdapterRelease(state.adapter) + wgpu.SurfaceRelease(state.surface) + wgpu.InstanceRelease(state.instance) +} diff --git a/vendor/wgpu/examples/sdl2/os_js.odin b/vendor/wgpu/examples/sdl2/os_js.odin new file mode 100644 index 000000000..9634f4afe --- /dev/null +++ b/vendor/wgpu/examples/sdl2/os_js.odin @@ -0,0 +1,60 @@ +package vendor_wgpu_example_triangle + +import "vendor:wgpu" +import "vendor:wasm/js" + +OS :: struct { + initialized: bool, +} + +@(private="file") +g_os: ^OS + +os_init :: proc(os: ^OS) { + g_os = os + assert(js.add_window_event_listener(.Resize, nil, size_callback)) +} + +// NOTE: frame loop is done by the runtime.js repeatedly calling `step`. +os_run :: proc(os: ^OS) { + os.initialized = true +} + +os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) { + rect := js.get_bounding_client_rect("body") + return u32(rect.width), u32(rect.height) +} + +os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface { + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromCanvasHTMLSelector{ + sType = .SurfaceDescriptorFromCanvasHTMLSelector, + selector = "#wgpu-canvas", + }, + }, + ) +} + +@(private="file", export) +step :: proc(dt: f32) -> bool { + if !g_os.initialized { + return true + } + + frame(dt) + return true +} + +@(private="file", fini) +os_fini :: proc() { + js.remove_window_event_listener(.Resize, nil, size_callback) + + finish() +} + +@(private="file") +size_callback :: proc(e: js.Event) { + resize() +} diff --git a/vendor/wgpu/examples/sdl2/os_sdl2.odin b/vendor/wgpu/examples/sdl2/os_sdl2.odin new file mode 100644 index 000000000..0e6c5b57a --- /dev/null +++ b/vendor/wgpu/examples/sdl2/os_sdl2.odin @@ -0,0 +1,85 @@ +//+build !js +package vendor_wgpu_example_triangle + +import "core:c" +import "core:fmt" + +import "vendor:sdl2" +import "vendor:wgpu" +import "vendor:wgpu/sdl2glue" + +OS :: struct { + window: ^sdl2.Window, +} + +os_init :: proc(os: ^OS) { + sdl_flags := sdl2.InitFlags{.VIDEO, .JOYSTICK, .GAMECONTROLLER, .EVENTS} + if res := sdl2.Init(sdl_flags); res != 0 { + fmt.eprintfln("ERROR: Failed to initialize SDL: [%s]", sdl2.GetError()) + return + } + + window_flags: sdl2.WindowFlags = {.SHOWN, .ALLOW_HIGHDPI, .RESIZABLE} + os.window = sdl2.CreateWindow( + "WGPU Native Triangle", + sdl2.WINDOWPOS_CENTERED, + sdl2.WINDOWPOS_CENTERED, + 960, + 540, + window_flags, + ) + if os.window == nil { + fmt.eprintfln("ERROR: Failed to create the SDL Window: [%s]", sdl2.GetError()) + return + } + + sdl2.AddEventWatch(size_callback, nil) +} + +os_run :: proc(os: ^OS) { + now := sdl2.GetPerformanceCounter() + last : u64 + dt: f32 + main_loop: for { + last = now + now = sdl2.GetPerformanceCounter() + dt = f32((now - last) * 1000) / f32(sdl2.GetPerformanceFrequency()) + + e: sdl2.Event + + for sdl2.PollEvent(&e) { + #partial switch (e.type) { + case .QUIT: + break main_loop + } + } + + frame(dt) + } + + sdl2.DestroyWindow(os.window) + sdl2.Quit() + + finish() +} + + +os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) { + iw, ih: c.int + sdl2.GetWindowSize(os.window, &iw, &ih) + return u32(iw), u32(ih) +} + +os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface { + return sdl2glue.GetSurface(instance, os.window) +} + +@(private="file") +size_callback :: proc "c" (userdata: rawptr, event: ^sdl2.Event) -> c.int { + if event.type == .WINDOWEVENT { + if event.window.event == .SIZE_CHANGED || event.window.event == .RESIZED { + resize() + } + } + return 0 +} diff --git a/vendor/wgpu/examples/sdl2/web/index.html b/vendor/wgpu/examples/sdl2/web/index.html new file mode 100644 index 000000000..61872e35a --- /dev/null +++ b/vendor/wgpu/examples/sdl2/web/index.html @@ -0,0 +1,23 @@ + + + + + + WGPU WASM Triangle + + + + + + + + + diff --git a/vendor/wgpu/glfwglue/glue_linux.odin b/vendor/wgpu/glfwglue/glue_linux.odin index 35c36a37d..45d29a638 100644 --- a/vendor/wgpu/glfwglue/glue_linux.odin +++ b/vendor/wgpu/glfwglue/glue_linux.odin @@ -3,11 +3,8 @@ package wgpu_glfw_glue import "vendor:glfw" import "vendor:wgpu" -// GLFW needs to be compiled with wayland support for this to work. -SUPPORT_WAYLAND :: #config(WGPU_GFLW_GLUE_SUPPORT_WAYLAND, false) - GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface { - when SUPPORT_WAYLAND { + if glfw.GetPlatform != nil { if glfw.GetPlatform() == glfw.PLATFORM_WAYLAND { display := glfw.GetWaylandDisplay() surface := glfw.GetWaylandWindow(window) @@ -24,6 +21,10 @@ GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.S }, ) } + + if glfw.GetPlatform() != glfw.PLATFORM_X11 { + panic("wgpu glfw glue: unsupported platform, expected Wayland or X11") + } } display := glfw.GetX11Display() diff --git a/vendor/wgpu/sdl2glue/glue.odin b/vendor/wgpu/sdl2glue/glue.odin new file mode 100644 index 000000000..9da9a0738 --- /dev/null +++ b/vendor/wgpu/sdl2glue/glue.odin @@ -0,0 +1,6 @@ +//+build !linux +//+build !windows +//+build !darwin +package wgpu_sdl2_glue + +#panic("package wgpu/sdl2glue is not supported on the current target") diff --git a/vendor/wgpu/sdl2glue/glue_darwin.odin b/vendor/wgpu/sdl2glue/glue_darwin.odin new file mode 100644 index 000000000..c48b8488c --- /dev/null +++ b/vendor/wgpu/sdl2glue/glue_darwin.odin @@ -0,0 +1,25 @@ +package wgpu_sdl2_glue + +import "vendor:sdl2" +import "vendor:wgpu" +import CA "vendor:darwin/QuartzCore" +import NS "core:sys/darwin/Foundation" + +GetSurface :: proc(instance: wgpu.Instance, window: ^sdl2.Window) -> wgpu.Surface { + window_info: sdl2.SysWMinfo + sdl2.GetWindowWMInfo(window, &window_info) + ns_window := cast(^NS.Window)window_info.info.cocoa.window + metal_layer := CA.MetalLayer_layer() + ns_window->contentView()->setLayer(metal_layer) + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromMetalLayer{ + chain = wgpu.ChainedStruct{ + sType = .SurfaceDescriptorFromMetalLayer, + }, + layer = rawptr(metal_layer), + }, + }, + ) +} diff --git a/vendor/wgpu/sdl2glue/glue_linux.odin b/vendor/wgpu/sdl2glue/glue_linux.odin new file mode 100644 index 000000000..b01df251a --- /dev/null +++ b/vendor/wgpu/sdl2glue/glue_linux.odin @@ -0,0 +1,42 @@ +package wgpu_sdl2_glue + +import "vendor:sdl2" +import "vendor:wgpu" + +GetSurface :: proc(instance: wgpu.Instance, window: ^sdl2.Window) -> wgpu.Surface { + window_info: sdl2.SysWMinfo + sdl2.GetWindowWMInfo(window, &window_info) + if window_info.subsystem == .WAYLAND { + display := window_info.info.wl.display + surface := window_info.info.wl.surface + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromWaylandSurface{ + chain = { + sType = .SurfaceDescriptorFromWaylandSurface, + }, + display = display, + surface = surface, + }, + }, + ) + } else if window_info.subsystem == .X11 { + display := window_info.info.x11.display + window := window_info.info.x11.window + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromXlibWindow{ + chain = { + sType = .SurfaceDescriptorFromXlibWindow, + }, + display = display, + window = u64(window), + }, + }, + ) + } else { + panic("wgpu sdl2 glue: unsupported platform, expected Wayland or X11") + } +} diff --git a/vendor/wgpu/sdl2glue/glue_windows.odin b/vendor/wgpu/sdl2glue/glue_windows.odin new file mode 100644 index 000000000..a2b1437ab --- /dev/null +++ b/vendor/wgpu/sdl2glue/glue_windows.odin @@ -0,0 +1,25 @@ +package wgpu_sdl2_glue + +import win "core:sys/windows" + +import "vendor:sdl2" +import "vendor:wgpu" + +GetSurface :: proc(instance: wgpu.Instance, window: ^sdl2.Window) -> wgpu.Surface { + window_info: sdl2.SysWMinfo + sdl2.GetWindowWMInfo(window, &window_info) + hwnd := window_info.info.win.window + hinstance := win.GetModuleHandleW(nil) + return wgpu.InstanceCreateSurface( + instance, + &wgpu.SurfaceDescriptor{ + nextInChain = &wgpu.SurfaceDescriptorFromWindowsHWND{ + chain = wgpu.ChainedStruct{ + sType = .SurfaceDescriptorFromWindowsHWND, + }, + hinstance = rawptr(hinstance), + hwnd = rawptr(hwnd), + }, + }, + ) +} diff --git a/vendor/wgpu/wgpu.odin b/vendor/wgpu/wgpu.odin index 15d7fd61c..af81dde56 100644 --- a/vendor/wgpu/wgpu.odin +++ b/vendor/wgpu/wgpu.odin @@ -716,12 +716,7 @@ BufferDescriptor :: struct { mappedAtCreation: b32, } -Color :: struct { - r: f64, - g: f64, - b: f64, - a: f64, -} +Color :: [4]f64 CommandBufferDescriptor :: struct { nextInChain: ^ChainedStruct, @@ -1636,3 +1631,73 @@ SurfaceGetCurrentTexture :: proc(surface: Surface) -> (surface_texture: SurfaceT RawSurfaceGetCurrentTexture(surface, &surface_texture) return } + +// WGPU Native bindings + +BINDINGS_VERSION :: [4]u8{0, 19, 4, 1} +BINDINGS_VERSION_STRING :: "0.19.4.1" + +when ODIN_OS != .JS { + @(private="file", init) + wgpu_native_version_check :: proc() { + v := (transmute([4]u8)GetVersion()).wzyx + + if v != BINDINGS_VERSION { + buf: [1024]byte + n := copy(buf[:], "wgpu-native version mismatch: ") + n += copy(buf[n:], "bindings are for version ") + n += copy(buf[n:], BINDINGS_VERSION_STRING) + n += copy(buf[n:], ", but a different version is linked") + panic(string(buf[:n])) + } + } + + @(link_prefix="wgpu") + foreign libwgpu { + @(link_name="wgpuGenerateReport") + RawGenerateReport :: proc(instance: Instance, report: ^GlobalReport) --- + @(link_name="wgpuInstanceEnumerateAdapters") + RawInstanceEnumerateAdapters :: proc(instance: Instance, /* NULLABLE */ options: /* const */ ^InstanceEnumerateAdapterOptions, adapters: [^]Adapter) -> uint --- + + @(link_name="wgpuQueueSubmitForIndex") + RawQueueSubmitForIndex :: proc(queue: Queue, commandCount: uint, commands: [^]CommandBuffer) -> SubmissionIndex --- + + // Returns true if the queue is empty, or false if there are more queue submissions still in flight. + DevicePoll :: proc(device: Device, wait: b32, /* NULLABLE */ wrappedSubmissionIndex: /* const */ ^WrappedSubmissionIndex = nil) -> b32 --- + + SetLogCallback :: proc(callback: LogCallback, userdata: rawptr) --- + + SetLogLevel :: proc(level: LogLevel) --- + + GetVersion :: proc() -> u32 --- + + RenderPassEncoderSetPushConstants :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: rawptr) --- + + RenderPassEncoderMultiDrawIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- + RenderPassEncoderMultiDrawIndexedIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- + + RenderPassEncoderMultiDrawIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) --- + RenderPassEncoderMultiDrawIndexedIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) --- + + ComputePassEncoderBeginPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder, querySet: QuerySet, queryIndex: u32) --- + ComputePassEncoderEndPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder) --- + RenderPassEncoderBeginPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder, querySet: QuerySet, queryIndex: u32) --- + RenderPassEncoderEndPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder) --- + } + + GenerateReport :: proc(instance: Instance) -> (report: GlobalReport) { + RawGenerateReport(instance, &report) + return + } + + InstanceEnumerateAdapters :: proc(instance: Instance, options: ^InstanceEnumerateAdapterOptions = nil, allocator := context.allocator) -> (adapters: []Adapter) { + count := RawInstanceEnumerateAdapters(instance, options, nil) + adapters = make([]Adapter, count, allocator) + RawInstanceEnumerateAdapters(instance, options, raw_data(adapters)) + return + } + + QueueSubmitForIndex :: proc(queue: Queue, commands: []CommandBuffer) -> SubmissionIndex { + return RawQueueSubmitForIndex(queue, len(commands), raw_data(commands)) + } +} diff --git a/vendor/wgpu/wgpu_js.odin b/vendor/wgpu/wgpu_js.odin index f375a0d69..3c8375adb 100644 --- a/vendor/wgpu/wgpu_js.odin +++ b/vendor/wgpu/wgpu_js.odin @@ -22,5 +22,6 @@ wgpu_alloc :: proc "contextless" (size: i32) -> [^]byte { @(private="file", export) wgpu_free :: proc "contextless" (ptr: rawptr) { context = g_context - assert(free(ptr) == nil, "wgpu_free failed") + err := free(ptr) + assert(err == nil, "wgpu_free failed") } diff --git a/vendor/wgpu/wgpu_native.odin b/vendor/wgpu/wgpu_native.odin deleted file mode 100644 index 7c106a452..000000000 --- a/vendor/wgpu/wgpu_native.odin +++ /dev/null @@ -1,68 +0,0 @@ -//+build !js -package wgpu - -BINDINGS_VERSION :: [4]u8{0, 19, 4, 1} -BINDINGS_VERSION_STRING :: "0.19.4.1" - -@(private="file", init) -wgpu_native_version_check :: proc() { - v := (transmute([4]u8)GetVersion()).wzyx - - if v != BINDINGS_VERSION { - buf: [1024]byte - n := copy(buf[:], "wgpu-native version mismatch: ") - n += copy(buf[n:], "bindings are for version ") - n += copy(buf[n:], BINDINGS_VERSION_STRING) - n += copy(buf[n:], ", but a different version is linked") - panic(string(buf[:n])) - } -} - -@(link_prefix="wgpu") -foreign { - @(link_name="wgpuGenerateReport") - RawGenerateReport :: proc(instance: Instance, report: ^GlobalReport) --- - @(link_name="wgpuInstanceEnumerateAdapters") - RawInstanceEnumerateAdapters :: proc(instance: Instance, /* NULLABLE */ options: /* const */ ^InstanceEnumerateAdapterOptions, adapters: [^]Adapter) -> uint --- - - @(link_name="wgpuQueueSubmitForIndex") - RawQueueSubmitForIndex :: proc(queue: Queue, commandCount: uint, commands: [^]CommandBuffer) -> SubmissionIndex --- - - // Returns true if the queue is empty, or false if there are more queue submissions still in flight. - DevicePoll :: proc(device: Device, wait: b32, /* NULLABLE */ wrappedSubmissionIndex: /* const */ ^WrappedSubmissionIndex = nil) -> b32 --- - - SetLogCallback :: proc(callback: LogCallback, userdata: rawptr) --- - - SetLogLevel :: proc(level: LogLevel) --- - - GetVersion :: proc() -> u32 --- - - RenderPassEncoderSetPushConstants :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: cstring) --- - - RenderPassEncoderMultiDrawIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- - RenderPassEncoderMultiDrawIndexedIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- - - RenderPassEncoderMultiDrawIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) --- - RenderPassEncoderMultiDrawIndexedIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) --- - - ComputePassEncoderBeginPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder, querySet: QuerySet, queryIndex: u32) --- - ComputePassEncoderEndPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder) --- - RenderPassEncoderBeginPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder, querySet: QuerySet, queryIndex: u32) --- - RenderPassEncoderEndPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder) --- -} - -GenerateReport :: proc(instance: Instance) -> (report: GlobalReport) { - RawGenerateReport(instance, &report) - return -} - -InstanceEnumerateAdapters :: proc(instance: Instance, options: ^InstanceEnumerateAdapterOptions = nil, allocator := context.allocator) -> (adapters: []Adapter) { - count := RawInstanceEnumerateAdapters(instance, options, nil) - adapters = make([]Adapter, count, allocator) - RawInstanceEnumerateAdapters(instance, options, raw_data(adapters)) - return -} - -QueueSubmitForIndex :: proc(queue: Queue, commands: []CommandBuffer) -> SubmissionIndex { - return RawQueueSubmitForIndex(queue, len(commands), raw_data(commands)) -} diff --git a/vendor/x11/xlib/xlib_const.odin b/vendor/x11/xlib/xlib_const.odin index 0b87ab9f7..ce31a4e76 100644 --- a/vendor/x11/xlib/xlib_const.odin +++ b/vendor/x11/xlib/xlib_const.odin @@ -1,6 +1,80 @@ //+build linux, freebsd, openbsd package xlib +/* ---- X11/extensions/XKB.h ---------------------------------------------------------*/ + +XkbMinLegalKeyCode :: 8 +XkbMaxLegalKeyCode :: 255 +XkbMaxKeyCount :: XkbMaxLegalKeyCode - XkbMinLegalKeyCode + 1 +XkbPerKeyBitArraySize :: (XkbMaxLegalKeyCode + 1) / 8 +XkbKeyNameLength :: 4 +XkbNumVirtualMods :: 16 +XkbNumIndicators :: 32 +XkbNumKbdGroups :: 4 +XkbAnyActionDataSize :: 7 +XkbUseCoreKbd :: 0x0100 +XkbActionMessageLength :: 6 + +XkbInfoMask :: bit_set[XkbInfoMaskBits; int] +XkbInfoMaskBits :: enum u32 { + KeyTypes = 0, + KeySyms = 1, + ModifierMap = 2, + ExplicitComponents = 3, + KeyActions = 4, + KeyBehaviors = 5, + VirtualMods = 6, + VirtualModMap = 7, +} + +XkbAllClientInfoMask :: XkbInfoMask { + .KeyTypes, + .KeySyms, + .ModifierMap, +} + +XkbAllServerInfoMask :: XkbInfoMask { + .ExplicitComponents, + .KeyActions, + .KeyBehaviors, + .VirtualMods, + .VirtualModMap, +} + +XkbEventMask :: bit_set[XkbEventType; int] +XkbEventType :: enum i32 { + NewKeyboardNotify = 0, + MapNotify = 1, + StateNotify = 2, + ControlsNotify = 3, + IndicatorStateNotify = 4, + IndicatorMapNotify = 5, + NamesNotify = 6, + CompatMapNotify = 7, + BellNotify = 8, + ActionMessage = 9, + AccessXNotify = 10, + ExtensionDeviceNotify = 11, +} + +XkbAllEventsMask :: XkbEventMask { + .NewKeyboardNotify, + .MapNotify, + .StateNotify, + .ControlsNotify, + .IndicatorStateNotify, + .IndicatorMapNotify, + .NamesNotify, + .CompatMapNotify, + .BellNotify, + .ActionMessage, + .AccessXNotify, + .ExtensionDeviceNotify, +} + + +/* ---- X11/Xlib.h ---------------------------------------------------------*/ + // Special values for many types. Most of these constants // aren't attached to a specific type. diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 20ec5bb39..207b3f6bc 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -49,7 +49,7 @@ foreign xlib { DisplayString :: proc(display: ^Display) -> cstring --- // Display macros (defaults) DefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- - DefaultDepth :: proc(display: ^Display) -> i32 --- + DefaultDepth :: proc(display: ^Display, screen_no: i32) -> i32 --- DefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- DefaultRootWindow :: proc(display: ^Display) -> Window --- DefaultScreen :: proc(display: ^Display) -> i32 --- @@ -79,7 +79,7 @@ foreign xlib { DoesBackingStore :: proc(screen: ^Screen) -> BackingStore --- DoesSaveUnders :: proc(screen: ^Screen) -> b32 --- DisplayOfScreen :: proc(screen: ^Screen) -> ^Display --- - ScreenNumberOfScreens :: proc(screen: ^Screen) -> i32 --- + ScreenNumberOfScreen :: proc(screen: ^Screen) -> i32 --- EventMaskOfScreen :: proc(screen: ^Screen) -> EventMask --- WidthOfScreen :: proc(screen: ^Screen) -> i32 --- HeightOfScreen :: proc(screen: ^Screen) -> i32 --- @@ -138,8 +138,8 @@ foreign xlib { width: u32, height: u32, bordersz: u32, - border: int, - bg: int, + border: uint, + bg: uint, ) -> Window --- DestroyWindow :: proc(display: ^Display, window: Window) --- DestroySubwindows :: proc(display: ^Display, window: Window) --- @@ -1637,6 +1637,13 @@ foreign xlib { wm_hints: ^XWMHints, class_hints: ^XClassHint, ) --- + OpenIM :: proc( + display: ^Display, + rdb: XrmHashBucket, + res_name: cstring, + res_class: cstring, + ) -> XIM --- + SetLocaleModifiers :: proc(modifiers: cstring) -> cstring --- } @(default_calling_convention="c") @@ -1938,4 +1945,52 @@ foreign xlib { XrmInitialize :: proc() --- XrmGetStringDatabase :: proc(data: cstring) -> XrmDatabase --- XrmGetResource :: proc(db: XrmDatabase, name: cstring, class: cstring, type_return: ^cstring, val_return: ^XrmValue) -> b32 --- + + /* ---- X11/XKBlib.h ---------------------------------------------------------*/ + + XkbQueryExtension :: proc( + display: ^Display, + opcode_return: ^i32, + event_base_return: ^i32, + error_base_return: ^i32, + major_return: ^i32, + minor_return: ^i32, + ) -> b32 --- + XkbUseExtension :: proc( + display: ^Display, + major_return: ^i32, + minor_return: ^i32, + ) -> b32 --- + XkbGetMap :: proc( + display: ^Display, + which: XkbInfoMask, + device_spec: i32, + ) -> XkbDescPtr --- + XkbGetUpdatedMap :: proc( + display: ^Display, + which: XkbInfoMask, + desc: XkbDescPtr, + ) -> b32 --- + XkbSelectEvents :: proc( + display: ^Display, + deviceID: u32, + bits_to_change: XkbEventMask, + values: XkbEventMask, + ) -> b32 --- + XkbSetDetectableAutoRepeat :: proc( + display: ^Display, + detectable: b32, + supported: ^b32, + ) -> b32 --- + XkbGetState :: proc ( + display: ^Display, + device_spec: u32, + return_state: XkbStatePtr, + ) -> Status --- + XkbGetKeySyms :: proc( + display: ^Display, + first: u32, + num: u32, + xkb: XkbDescPtr, + ) -> Status --- } diff --git a/vendor/x11/xlib/xlib_types.odin b/vendor/x11/xlib/xlib_types.odin index 8cd0131fe..d2bd2c5a3 100644 --- a/vendor/x11/xlib/xlib_types.odin +++ b/vendor/x11/xlib/xlib_types.odin @@ -1006,6 +1006,767 @@ XConnectionWatchProc :: #type proc "c" ( opening: b32, watch_data: rawptr) +/* ---- X11/extensions/XKBlib.h ---------------------------------------------------------*/ + +XkbAnyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: u32, +} + +XkbNewKeyboardNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + old_device: i32, + min_key_code: i32, + max_key_code: i32, + old_min_key_code: i32, + old_max_key_code: i32, + changed: u32, + req_major: i8, + req_minor: i8, +} + +XkbMapNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + flags: u32, + first_type: i32, + num_types: i32, + min_key_code: KeyCode, + max_key_code: KeyCode, + first_key_sym: KeyCode, + first_key_act: KeyCode, + first_key_behavior: KeyCode, + first_key_explicit: KeyCode, + first_modmap_key: KeyCode, + first_vmodmap_key: KeyCode, + num_key_syms: i32, + num_key_acts: i32, + num_key_behaviors: i32, + num_key_explicit: i32, + num_modmap_keys: i32, + num_vmodmap_keys: i32, + vmods: u32, +} + +XkbStateNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + group: i32, + base_group: i32, + latched_group: i32, + locked_group: i32, + mods: u32, + base_mods: u32, + latched_mods: u32, + locked_mods: u32, + compat_state: i32, + grab_mods: u8, + compat_grab_mods: u8, + lookup_mods: u8, + compat_lookup_mods: u8, + ptr_buttons: i32, + keycode: KeyCode, + event_type: i8, // should be EventType but needs to be i8 instead of i32 + req_major: i8, + req_minor: i8, +} + +XkbControlsNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed_ctrls: u32, + enabled_ctrls: u32, + enabled_ctrls_changes: u32, + num_groups: i32, + keycode: KeyCode, + event_type: i8, + req_major: i8, + req_minor: i8, +} + +XkbIndicatorNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + state: u32, +} + +XkbNamesNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed: u32, + first_type: i32, + num_types: i32, + first_lvl: i32, + num_lvls: i32, + num_aliases: i32, + num_radio_groups: i32, + changed_vmods: u32, + changed_groups: u32, + changed_indicators: u32, + first_key: i32, + num_keys: i32, +} + +XkbCompatMapNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + changed_groups: u32, + first_si: i32, + num_si: i32, + num_total_si: i32, +} + +XkbBellNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + percent: i32, + pitch: i32, + duration: i32, + bell_class: i32, + bell_id: i32, + name: Atom, + window: Window, + event_only: b32, +} + +XkbActionMessageEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + keycode: KeyCode, + press: b32, + key_event_follows: b32, + group: i32, + mods: u32, + message: [XkbActionMessageLength+1]i8, +} + +XkbAccessXNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + detail: i32, + keycode: i32, + sk_delay: i32, + debounce_delay: i32, +} + +XkbExtensionDeviceNotifyEvent :: struct { + type: i32, + serial: u64, + send_event: b32, + display: ^Display, + time: Time, + xkb_type: XkbEventType, + device: i32, + reason: u32, + supported: u32, + unsupported: u32, + first_btn: i32, + num_btns: i32, + leds_defined: u32, + led_state: u32, + led_class: i32, + led_id: i32, +} + +XkbEvent :: struct #raw_union { + type: XkbEventType, + any: XkbAnyEvent, + new_kbd: XkbNewKeyboardNotifyEvent, + _map: XkbMapNotifyEvent, + state: XkbStateNotifyEvent, + ctrls: XkbControlsNotifyEvent, + indicators: XkbIndicatorNotifyEvent, + names: XkbNamesNotifyEvent, + compat: XkbCompatMapNotifyEvent, + bell: XkbBellNotifyEvent, + message: XkbActionMessageEvent, + accessx: XkbAccessXNotifyEvent, + device: XkbExtensionDeviceNotifyEvent, + core: XEvent, +} + +/* ---- X11/extensions/XKBgeom.h ---------------------------------------------------------*/ + +XkbPointRec :: struct { + x: i16, + y: i16, +} +XkbPointPtr :: ^XkbPointRec + +XkbBoundsRec :: struct { + x1, x2: i16, + y1, y2: i16, +} +XkbBoundsPtr :: ^XkbBoundsRec + +XkbOutlineRec :: struct { + num_points: u16, + sz_points: u16, + corner_radius: u16, + points: [^]XkbPointRec, +} +XkbOutlinePtr :: ^XkbOutlineRec + +XkbShapeRec :: struct { + name: Atom, + num_outlines: u16, + sz_outlines: u16, + outlines: [^]XkbOutlineRec, + approx: XkbOutlinePtr, + primary: XkbOutlinePtr, + bounds: XkbBoundsRec, +} +XkbShapePtr :: ^XkbShapeRec + +XkbPropertyRec :: struct { + name: cstring, + value: cstring, +} +XkbPropertyPtr :: ^XkbPropertyRec + +XkbColorRec :: struct { + pixel: u32, + spec: ^u8, // cstring? +} +XkbColorPtr :: ^XkbColorRec + +XkbKeyRec :: struct { + name: XkbKeyNameRec, + gap: i16, + shape_ndx: u8, + color_ndx: u8, +} +XkbKeyPtr :: ^XkbKeyRec + +XkbRowRec :: struct { + top: i16, + left: i16, + num_keys: u16, + sz_keys: u16, + vertical: i32, + keys: [^]XkbKeyRec, + bounds: XkbBoundsRec, +} +XkbRowPtr :: ^XkbRowRec + +XkbAnyDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, +} +XkbAnyDoodadPtr :: ^XkbAnyDoodadRec + +XkbShapeDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + shape_ndx: u16, +} +XkbShapeDoodadPtr :: ^XkbShapeDoodadRec + +XkbTextDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + text: cstring, + font: cstring, +} +XkbTextDoodadPtr :: ^XkbTextDoodadRec + +XkbIndicatorDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + on_color_ndx: u16, + off_color_ndx: u16, +} +XkbIndicatorDoodadPtr :: ^XkbIndicatorDoodadRec + +XkbLogoDoodadRec :: struct { + name: Atom, + type: u8, + priority: u8, + top: i16, + left: i16, + angle: i16, + color_ndx: u16, + shape_ndx: u16, + logo_name: cstring, +} +XkbLogoDoodadPtr :: ^XkbLogoDoodadRec + +XkbDoodadRec :: struct #raw_union { + any: XkbAnyDoodadRec, + shape: XkbShapeDoodadRec, + text: XkbTextDoodadRec, + indicator: XkbIndicatorDoodadRec, + logo: XkbLogoDoodadRec, +} +XkbDoodadPtr :: ^XkbDoodadRec + +XkbOverlayKeyRec :: struct { + over: XkbKeyNameRec, + under: XkbKeyNameRec, +} +XkbOverlayKeyPtr :: ^XkbOverlayKeyRec + +XkbOverlayRowRec :: struct { + row_under: u16, + num_keys: u16, + sz_keys: u16, + keys: [^]XkbOverlayKeyRec, +} +XkbOverlayRowPtr :: ^XkbOverlayRowRec + +XkbOverlayRec :: struct { + name: Atom, + section_under: XkbSectionPtr, + num_rows: u16, + sz_rows: u16, + rows: [^]XkbOverlayRowRec, + bounds: [^]XkbBoundsRec, +} +XkbOverlayPtr :: ^XkbOverlayRec + +XkbSectionRec :: struct { + name: Atom, + priority: u8, + top: i16, + left: i16, + width: u16, + height: u16, + angle: i16, + num_rows: u16, + num_doodads: u16, + num_overlays: u16, + sz_rows: u16, + sz_doodads: u16, + sz_overlays: u16, + rows: [^]XkbRowRec, + doodads: [^]XkbDoodadRec, + bounds: XkbBoundsRec, + overlays: [^]XkbOverlayRec, +} +XkbSectionPtr :: ^XkbSectionRec + +XkbGeometryRec :: struct { + name: Atom, + width_mm: u16, + height_mm: u16, + label_font: cstring, + label_color: XkbColorPtr, + base_color: XkbColorPtr, + sz_properties: u16, + sz_colors: u16, + sz_shapes: u16, + sz_sections: u16, + sz_doodads: u16, + sz_key_aliases: u16, + num_properties: u16, + num_colors: u16, + num_shapes: u16, + num_sections: u16, + num_doodads: u16, + num_key_aliases: u16, + properties: [^]XkbPropertyRec, + colors: [^]XkbColorRec, + shapes: [^]XkbShapeRec, + sections: [^]XkbSectionRec, + doodads: [^]XkbDoodadRec, + key_aliases: [^]XkbKeyAliasRec, +} +XkbGeometryPtr :: ^XkbGeometryRec + + +/* ---- X11/extensions/XKBstr.h ---------------------------------------------------------*/ + +XkbStateRec :: struct { + group: u8, + locked_group: u8, + base_group: u16, + latched_group: u16, + mods: u8, + base_mods: u8, + latched_mods: u8, + locked_mods: u8, + compat_state: u8, + grab_mods: u8, + compat_grab_mods: u8, + lookup_mods: u8, + compat_lookup_mods: u8, + ptr_buttons: u16, +} +XkbStatePtr :: ^XkbStateRec + +XkbModsRec :: struct { + mask: u8, /* effective mods */ + real_mods: u8, + vmods: u16, +} +XkbModsPtr :: ^XkbModsRec + +XkbKTMapEntryRec :: struct { + active: b32, + level: u8, + mods: XkbModsRec, +} +XkbKTMapEntryPtr :: ^XkbKTMapEntryRec + +XkbKeyTypeRec :: struct { + mod: XkbModsRec, + num_levels: u8, + map_count: u8, + _map: [^]XkbKTMapEntryRec, + preserve: [^]XkbModsRec, + name: Atom, + level_names: [^]Atom, +} +XkbKeyTypePtr :: ^XkbKeyTypeRec + +XkbBehavior :: struct { + type: u8, + data: u8, +} + +XkbAnyAction :: struct { + type: u8, + data: [XkbAnyActionDataSize]u8, +} + +XkbModAction :: struct { + type: u8, + flags: u8, + mask: u8, + real_mods: u8, + vmods1: u8, + vmods2: u8, +} + +XkbGroupAction :: struct { + type: u8, + flags: u8, + group_XXX: i8, +} + +XkbISOAction :: struct { + type: u8, + flags: u8, + mask: u8, + real_mods: u8, + group_XXX: i8, + affect: u8, + vmods1: u8, + vmods2: u8, +} + +XkbPtrAction :: struct { + type: u8, + flags: u8, + high_XXX: u8, + low_XXX: u8, + high_YYY: u8, + low_YYY: u8, +} + +XkbPtrBtnAction :: struct { + type: u8, + flags: u8, + count: u8, + button: u8, +} + +XkbPtrDfltAction :: struct { + type: u8, + flags: u8, + affect: u8, + value_XXX: u8, +} + +XkbSwitchScreenAction :: struct { + type: u8, + flags: u8, + screenXXX: i8, +} + +XkbCtrlsAction :: struct { + type: u8, + flags: u8, + ctrls3: u8, + ctrls2: u8, + ctrls1: u8, + ctrls0: u8, +} + +XkbMessageAction :: struct { + type: u8, + flags: u8, + message: [6]u8, +} + +XkbRedirectKeyAction :: struct { + type: u8, + new_key: u8, + mods_mask: u8, + mods: u8, + vmods_mask0: u8, + vmods_mask1: u8, + vmods0: u8, + vmods1: u8, +} + +XkbDeviceBtnAction :: struct { + type: u8, + flags: u8, + count: u8, + button: u8, + device: u8, +} + +XkbDeviceValuatorAction :: struct { + type: u8, + device: u8, + v1_what: u8, + v1_ndx: u8, + v1_value: u8, + v2_what: u8, + v2_ndx: u8, + v2_value: u8, +} + +XkbAction :: struct #raw_union { + any: XkbAnyAction, + mod: XkbModAction, + group: XkbGroupAction, + iso: XkbISOAction, + ptr: XkbPtrAction, + btn: XkbPtrBtnAction, + dflt: XkbPtrDfltAction, + screen: XkbSwitchScreenAction, + ctrls: XkbCtrlsAction, + msg: XkbMessageAction, + redirect: XkbRedirectKeyAction, + devbtn: XkbDeviceBtnAction, + devval: XkbDeviceValuatorAction, + type: u8, +} + +XkbControlsRec :: struct { + mk_dflt_btn: u8, + num_groups: u8, + groups_wrap: u8, + internal: XkbModsRec, + ignore_lock: XkbModsRec, + enabled_ctrls: u32, + repeat_delay: u16, + repeat_interval: u16, + slow_keys_delay: u16, + debounce_delay: u16, + mk_delay: u16, + mk_interval: u16, + mk_time_to_max: u16, + mk_max_speed: u16, + mk_curve: i16, + ax_options: u16, + ax_timeout: u16, + axt_opts_mask: u16, + axt_opts_values: u16, + axt_ctrls_mask: u32, + axt_ctrls_values: u32, + per_key_repeat: [XkbPerKeyBitArraySize]u8, +} +XkbControlsPtr :: ^XkbControlsRec + +XkbServerMapRec :: struct { + num_acts: u16, + size_acts: u16, + acts: [^]XkbAction, + + behaviors: [^]XkbBehavior, + key_acts: [^]u16, + explicit: [^]u8, + vmods: [XkbNumVirtualMods]u8, + vmodmap: [^]u16, +} +XkbServerMapPtr :: ^XkbServerMapRec + +XkbSymMapRec :: struct { + kt_index: [XkbNumKbdGroups]u8, + group_info: u8, + width: u8, + offset: u16, +} +XkbSymMapPtr :: ^XkbSymMapRec + +XkbClientMapRec :: struct { + size_types: u8, + num_types: u8, + types: [^]XkbKeyTypeRec, + + size_syms: u16, + num_syms: u16, + syms: [^]XID, // Keysym + key_sym_map: [^]XkbSymMapRec, + + modmap: [^]u8, +} +XkbClientMapPtr :: ^XkbClientMapRec + +XkbSymInterpretRec :: struct { + sym: XID, // KeySym + flags: u8, + match: u8, + mods: u8, + virtual_mod: u8, + act: XkbAnyAction, +} +XkbSymInterpretPtr :: ^XkbSymInterpretRec + +XkbCompatMapRec :: struct { + sym_interpret: [^]XkbSymInterpretRec, + groups: [XkbNumKbdGroups]XkbModsRec, + num_si: u16, + size_si: u16, +} +XkbCompatMapPtr :: ^XkbCompatMapRec + +XkbIndicatorMapRec :: struct { + flags: u8, + which_groups: u8, + groups: u8, + which_mods: u8, + mods: XkbModsRec, + ctrls: u32, +} +XkbIndicatorMapPtr :: ^XkbIndicatorMapRec + +XkbIndicatorRec :: struct { + phys_indicators: u64, + maps: [XkbNumIndicators]XkbIndicatorMapRec, +} +XkbIndicatorPtr :: ^XkbIndicatorRec + +XkbKeyNameRec :: struct { + name: [XkbKeyNameLength]i8, // Non nul-terminated string +} +XkbKeyNamePtr :: ^XkbKeyNameRec + +XkbKeyAliasRec :: struct { + real: [XkbKeyNameLength]i8, // Non nul-terminated string + alias: [XkbKeyNameLength]i8, // Non nul-terminated string +} +XkbKeyAliasPtr :: ^XkbKeyAliasRec + +XkbNamesRec :: struct { + keycodes: Atom, + geometry: Atom, + symbols: Atom, + types: Atom, + compat: Atom, + vmods: [XkbNumVirtualMods]Atom, + indicators: [XkbNumIndicators]Atom, + groups: [XkbNumKbdGroups]Atom, + keys: [^]XkbKeyNameRec, + key_aliases: [^]XkbKeyAliasRec, + radio_groups: [^]Atom, + phys_symbol: Atom, + num_keys: u8, + num_key_aliases: u8, + num_rg: u16, +} +XkbNamesPtr :: ^XkbNamesRec + +XkbDescRec :: struct { + display: ^Display, + flags: u16, + device_spec: u16, + min_key_code: KeyCode, + max_key_code: KeyCode, + + ctrls: XkbControlsPtr, + server: XkbServerMapPtr, + _map: XkbClientMapPtr, + indicators: XkbIndicatorPtr, + names: XkbNamesPtr, + compat: XkbCompatMapPtr, + geom: XkbGeometryPtr, +} +XkbDescPtr :: ^XkbDescRec + + /* ---- X11/Xcms.h ---------------------------------------------------------*/ XcmsColorFormat :: uint