diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c9266328..e547959aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: usesh: true copyback: false prepare: | - PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/10.0_2024Q2/All" /usr/sbin/pkg_add pkgin + PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/amd64/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/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: | @@ -32,10 +32,9 @@ jobs: gmake -C vendor/miniaudio/src ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64 ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64 - ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false - ./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false - ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false - ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false + ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true (cd tests/issues; ./run.sh) build_freebsd: name: FreeBSD Build, Check, and Test @@ -61,10 +60,9 @@ jobs: gmake -C vendor/cgltf/src gmake -C vendor/miniaudio/src ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 - ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false - ./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false - ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false - ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false + ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true (cd tests/issues; ./run.sh) ci: strategy: @@ -118,15 +116,13 @@ jobs: - name: Odin check examples/all run: ./odin check examples/all -strict-style - name: Normal Core library tests - run: ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Optimized Core library tests - run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Vendor library tests - run: ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false + run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Internals tests - run: ./odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false - - name: Core library benchmarks - run: ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false + run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: GitHub Issue tests run: | cd tests/issues @@ -180,38 +176,33 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin run examples/demo -debug + odin run examples/demo -debug -vet -strict-style -disallow-do - name: Odin check examples/all shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin check examples/all -strict-style + odin check examples/all -vet -strict-style -disallow-do - name: Core library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Optimized core library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false - - name: Core library benchmarks - shell: cmd - run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false + odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Vendor library tests 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 + odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Odin internals tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false + odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Odin documentation tests shell: cmd run: | @@ -229,3 +220,53 @@ jobs: run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin check examples/all -strict-style -target:windows_i386 + + build_linux_riscv64: + runs-on: ubuntu-latest + name: Linux riscv64 (emulated) Build, Check and Test + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - name: Download LLVM (Linux) + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 18 + echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH + + - name: Build Odin + run: ./build_odin.sh release + + - name: Odin version + run: ./odin version + + - name: Odin report + run: ./odin report + + - name: Compile needed Vendor + run: | + make -C vendor/stb/src + make -C vendor/cgltf/src + make -C vendor/miniaudio/src + + - name: Odin check + run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do + + - name: Install riscv64 toolchain and qemu + run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross + + - name: Odin run + run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + + - name: Odin run -debug + run: ./odin run examples/demo -debug -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + + - name: Normal Core library tests + run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + + - name: Optimized Core library tests + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" + + - name: Internals tests + run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e4bc5d81c..0c5526d0f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -61,7 +61,6 @@ jobs: mkdir dist cp odin dist cp LICENSE dist - cp libLLVM* dist cp -r shared dist cp -r base dist cp -r core dist diff --git a/.gitignore b/.gitignore index 42b2d3d95..bfd5abeed 100644 --- a/.gitignore +++ b/.gitignore @@ -17,13 +17,12 @@ [Rr]eleases/ x64/ x86/ +!/core/simd/x86 bld/ [Bb]in/ [Oo]bj/ [Ll]og/ ![Cc]ore/[Ll]og/ -tests/documentation/verify/ -tests/documentation/all.odin-doc # Visual Studio 2015 cache/options directory .vs/ # Visual Studio Code options directory @@ -31,7 +30,6 @@ tests/documentation/all.odin-doc # 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/Makefile b/Makefile index 91010a620..ec848192a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: debug +all: default demo: ./odin run examples/demo/demo.odin -file @@ -6,12 +6,18 @@ demo: report: ./odin report +default: + PROGRAM=make ./build_odin.sh # debug + debug: ./build_odin.sh debug release: ./build_odin.sh release +release-native: + ./build_odin.sh release-native + release_native: ./build_odin.sh release-native diff --git a/README.md b/README.md index 4df71015d..b4bb6bac7 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,9 @@ Answers to common questions about Odin. Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections. -#### [The Odin Wiki](https://github.com/odin-lang/Odin/wiki) +#### [Odin Documentation](https://odin-lang.org/docs/) -A wiki maintained by the Odin community. +Documentation for the Odin language itself. #### [Odin Discord](https://discord.gg/sVBPHEv) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 37a42b904..3cf99bbd2 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -42,8 +42,8 @@ overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #option 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) --- +saturating_add :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- +saturating_sub :: 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))) --- @@ -219,14 +219,21 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info --- type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) --- type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) --- +type_has_shared_fields :: proc($U, $V: typeid) -> bool typeid where type_is_struct(U), type_is_struct(V) --- + constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- +constant_log2 :: proc($v: $T) -> T where type_is_integer(T) --- + // SIMD related simd_add :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_sub :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_mul :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_div :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_float(T) --- +simd_saturating_add :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) --- +simd_saturating_sub :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) --- + // Keeps Odin's Behaviour // (x << y) if y <= mask else 0 simd_shl :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- @@ -237,9 +244,6 @@ simd_shr :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- -simd_add_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_sub_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- - simd_bit_and :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_bit_or :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_bit_xor :: proc(a, b: #simd[N]T) -> #simd[N]T --- @@ -268,13 +272,28 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- simd_extract :: proc(a: #simd[N]T, idx: uint) -> T --- simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T --- -simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T --- -simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T --- -simd_reduce_min :: proc(a: #simd[N]T) -> T --- -simd_reduce_max :: proc(a: #simd[N]T) -> T --- -simd_reduce_and :: proc(a: #simd[N]T) -> T --- -simd_reduce_or :: proc(a: #simd[N]T) -> T --- -simd_reduce_xor :: proc(a: #simd[N]T) -> T --- +simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_or :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_xor :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- + +simd_reduce_any :: proc(a: #simd[N]T) -> T where type_is_boolean(T) --- +simd_reduce_all :: proc(a: #simd[N]T) -> T where type_is_boolean(T) --- + + +simd_gather :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- +simd_scatter :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- + +simd_masked_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- +simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- + +simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- +simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- + + simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T --- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- @@ -288,11 +307,11 @@ simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) --- -// equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0) -simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- +// equivalent to a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0) +simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- -simd_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- -simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- +simd_lanes_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- +simd_lanes_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- // Checks if the current target supports the given target features. // diff --git a/base/runtime/core.odin b/base/runtime/core.odin index e2490051e..90e049b0c 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -546,10 +546,23 @@ Odin_OS_Type :: type_of(ODIN_OS) arm64, wasm32, wasm64p32, + riscv64, } */ Odin_Arch_Type :: type_of(ODIN_ARCH) +Odin_Arch_Types :: bit_set[Odin_Arch_Type] + +ALL_ODIN_ARCH_TYPES :: Odin_Arch_Types{ + .amd64, + .i386, + .arm32, + .arm64, + .wasm32, + .wasm64p32, + .riscv64, +} + /* // Defined internally by the compiler Odin_Build_Mode_Type :: enum int { @@ -573,6 +586,22 @@ Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE) */ Odin_Endian_Type :: type_of(ODIN_ENDIAN) +Odin_OS_Types :: bit_set[Odin_OS_Type] + +ALL_ODIN_OS_TYPES :: Odin_OS_Types{ + .Windows, + .Darwin, + .Linux, + .Essence, + .FreeBSD, + .OpenBSD, + .NetBSD, + .Haiku, + .WASI, + .JS, + .Orca, + .Freestanding, +} /* // Defined internally by the compiler @@ -750,6 +779,10 @@ __init_context :: proc "contextless" (c: ^Context) { } default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! { + default_assertion_contextless_failure_proc(prefix, message, loc) +} + +default_assertion_contextless_failure_proc :: proc "contextless" (prefix, message: string, loc: Source_Code_Location) -> ! { when ODIN_OS == .Freestanding { // Do nothing } else { diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 38ad95be8..8157afe09 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -68,7 +68,7 @@ copy :: proc{copy_slice, copy_from_string} // Note: If you want the elements to remain in their order, use `ordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { +unordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) n := len(array)-1 if index != n { @@ -82,7 +82,7 @@ unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_loca // Note: If the elements do not have to remain in their order, prefer `unordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { +ordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { copy(array[index:], array[index+1:]) @@ -95,7 +95,7 @@ ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_locati // Note: This is an O(N) operation. // Note: If the range is out of bounds, this procedure will panic. @builtin -remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_location) #no_bounds_check { +remove_range :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) n := max(hi-lo, 0) if n > 0 { @@ -350,7 +350,7 @@ _make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, ali return } -// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// `make_map` allocates and initializes a map. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // // Note: Prefer using the procedure group `make`. @@ -362,7 +362,7 @@ make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (n: i @builtin -inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -620,7 +620,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast arg: E, } @builtin -inject_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 { +inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -643,7 +643,7 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: } @builtin -inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -668,7 +668,7 @@ inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string @builtin -assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if index < len(array) { array[index] = arg ok = true @@ -682,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, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int 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 @@ -699,7 +699,7 @@ assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: @builtin -assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(arg) if len(arg) == 0 { ok = true @@ -838,7 +838,7 @@ 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) { +shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc) } @@ -948,3 +948,30 @@ unimplemented :: proc(message := "", loc := #caller_location) -> ! { } p("not yet implemented", message, loc) } + + +@builtin +@(disabled=ODIN_DISABLE_ASSERT) +assert_contextless :: proc "contextless" (condition: bool, message := "", loc := #caller_location) { + if !condition { + // NOTE(bill): This is wrapped in a procedure call + // to improve performance to make the CPU not + // execute speculatively, making it about an order of + // magnitude faster + @(cold) + internal :: proc "contextless" (message: string, loc: Source_Code_Location) { + default_assertion_contextless_failure_proc("runtime assertion", message, loc) + } + internal(message, loc) + } +} + +@builtin +panic_contextless :: proc "contextless" (message: string, loc := #caller_location) -> ! { + default_assertion_contextless_failure_proc("panic", message, loc) +} + +@builtin +unimplemented_contextless :: proc "contextless" (message := "", loc := #caller_location) -> ! { + default_assertion_contextless_failure_proc("not yet implemented", message, loc) +} diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 7f7f5f086..2980b7a9e 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -76,7 +76,7 @@ raw_soa_footer :: proc{ @(builtin, require_results) -make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { +make_soa_aligned :: proc($T: typeid/#soa[]$E, #any_int length, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { if length <= 0 { return } @@ -135,7 +135,7 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc } @(builtin, require_results) -make_soa_slice :: proc($T: typeid/#soa[]$E, length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { +make_soa_slice :: proc($T: typeid/#soa[]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { return make_soa_aligned(T, length, align_of(E), allocator, loc) } @@ -172,7 +172,7 @@ make_soa :: proc{ @builtin -resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { +resize_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { if array == nil { return nil } @@ -183,7 +183,7 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_locat } @builtin -non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { +non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { if array == nil { return nil } @@ -194,12 +194,12 @@ non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #cal } @builtin -reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { +reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_soa(array, capacity, true, loc) } @builtin -non_zero_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { +non_zero_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_soa(array, capacity, false, loc) } @@ -484,7 +484,7 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E { // Note: If you the elements to remain in their order, use `ordered_remove_soa`. // Note: If the index is out of bounds, this procedure will panic. @builtin -unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check { +unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { ti := type_info_of(typeid_of(T)) @@ -512,7 +512,7 @@ unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #cal // Note: If you the elements do not have to remain in their order, prefer `unordered_remove_soa`. // Note: If the index is out of bounds, this procedure will panic. @builtin -ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check { +ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { ti := type_info_of(typeid_of(T)) diff --git a/base/runtime/default_allocators_nil.odin b/base/runtime/default_allocators_nil.odin index ce8519c10..e7a1b1a74 100644 --- a/base/runtime/default_allocators_nil.odin +++ b/base/runtime/default_allocators_nil.odin @@ -1,8 +1,8 @@ package runtime nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { switch mode { case .Alloc, .Alloc_Non_Zeroed: return nil, .Out_Of_Memory diff --git a/base/runtime/default_temp_allocator_arena.odin b/base/runtime/default_temp_allocator_arena.odin index ab9f7df4a..db157b267 100644 --- a/base/runtime/default_temp_allocator_arena.odin +++ b/base/runtime/default_temp_allocator_arena.odin @@ -129,7 +129,7 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio return } -// `arena_init` will initialize the arena with a usuable block. +// `arena_init` will initialize the arena with a usable block. // This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary @(require_results) arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error { diff --git a/base/runtime/entry_unix.odin b/base/runtime/entry_unix.odin index 7d7252625..5dfd37f99 100644 --- a/base/runtime/entry_unix.odin +++ b/base/runtime/entry_unix.odin @@ -34,6 +34,9 @@ when ODIN_BUILD_MODE == .Dynamic { } else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { @require foreign import entry "entry_unix_no_crt_darwin_arm64.asm" SYS_exit :: 1 + } else when ODIN_ARCH == .riscv64 { + @require foreign import entry "entry_unix_no_crt_riscv64.asm" + SYS_exit :: 93 } @(link_name="_start_odin", linkage="strong", require) _start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! { diff --git a/base/runtime/entry_unix_no_crt_riscv64.asm b/base/runtime/entry_unix_no_crt_riscv64.asm new file mode 100644 index 000000000..756515b72 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_riscv64.asm @@ -0,0 +1,10 @@ +.text + +.globl _start + +_start: + ld a0, 0(sp) + addi a1, sp, 8 + addi sp, sp, ~15 + call _start_odin + ebreak diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin index cdad8690e..a0a984f10 100644 --- a/base/runtime/heap_allocator.odin +++ b/base/runtime/heap_allocator.odin @@ -19,12 +19,15 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, // the pointer we return to the user. // - aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, Allocator_Error) { + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr, old_size: int, zero_memory := true) -> ([]byte, Allocator_Error) { a := max(alignment, align_of(rawptr)) space := size + a - 1 allocated_mem: rawptr - if old_ptr != nil { + + force_copy := old_ptr != nil && a > align_of(rawptr) + + if !force_copy && old_ptr != nil { original_old_ptr := ([^]rawptr)(old_ptr)[-1] allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) } else { @@ -36,12 +39,19 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) diff := int(aligned_ptr - ptr) if (size + diff) > space || allocated_mem == nil { + aligned_free(old_ptr) + aligned_free(allocated_mem) return nil, .Out_Of_Memory } aligned_mem = rawptr(aligned_ptr) ([^]rawptr)(aligned_mem)[-1] = allocated_mem + if force_copy { + mem_copy_non_overlapping(aligned_mem, old_ptr, old_size) + aligned_free(old_ptr) + } + return byte_slice(aligned_mem, size), nil } @@ -53,10 +63,10 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory := true) -> (new_memory: []byte, err: Allocator_Error) { if p == nil { - return nil, nil + return aligned_alloc(new_size, new_alignment, nil, old_size, zero_memory) } - new_memory = aligned_alloc(new_size, new_alignment, p, zero_memory) or_return + new_memory = aligned_alloc(new_size, new_alignment, p, old_size, zero_memory) or_return // NOTE: heap_resize does not zero the new memory, so we do it if zero_memory && new_size > old_size { @@ -68,7 +78,7 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, switch mode { case .Alloc, .Alloc_Non_Zeroed: - return aligned_alloc(size, alignment, nil, mode == .Alloc) + return aligned_alloc(size, alignment, nil, 0, mode == .Alloc) case .Free: aligned_free(old_memory) @@ -77,9 +87,6 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, return nil, .Mode_Not_Implemented case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return aligned_alloc(size, alignment, nil, mode == .Resize) - } return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize) case .Query_Features: diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 1a97ade09..ff60cf547 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -8,10 +8,9 @@ IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 @(private) RUNTIME_LINKAGE :: "strong" when ( - (ODIN_USE_SEPARATE_MODULES || + ODIN_USE_SEPARATE_MODULES || ODIN_BUILD_MODE == .Dynamic || - !ODIN_NO_CRT) && - !IS_WASM) else "internal" + !ODIN_NO_CRT) else "internal" RUNTIME_REQUIRE :: false // !ODIN_TILDE @(private) @@ -879,9 +878,6 @@ extendhfsf2 :: proc "c" (value: __float16) -> f32 { @(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) floattidf :: proc "c" (a: i128) -> f64 { -when IS_WASM { - return 0 -} else { DBL_MANT_DIG :: 53 if a == 0 { return 0.0 @@ -921,14 +917,10 @@ when IS_WASM { fb[0] = u32(a) // mantissa-low return transmute(f64)fb } -} @(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) floattidf_unsigned :: proc "c" (a: u128) -> f64 { -when IS_WASM { - return 0 -} else { DBL_MANT_DIG :: 53 if a == 0 { return 0.0 @@ -966,7 +958,6 @@ when IS_WASM { fb[0] = u32(a) // mantissa-low return transmute(f64)fb } -} @@ -1023,14 +1014,32 @@ modti3 :: proc "c" (a, b: i128) -> i128 { @(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { - u := udivmod128(u128(a), u128(b), (^u128)(rem)) - return i128(u) + s_a := a >> (128 - 1) // -1 if negative or 0 + s_b := b >> (128 - 1) + an := (a ~ s_a) - s_a // absolute + bn := (b ~ s_b) - s_b + + s_b ~= s_a // quotient sign + u_s_b := u128(s_b) + u_s_a := u128(s_a) + + r: u128 = --- + u := i128((udivmodti4(u128(an), u128(bn), &r) ~ u_s_b) - u_s_b) // negate if negative + rem^ = i128((r ~ u_s_a) - u_s_a) + return u } @(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(u128(a), u128(b), nil) - return i128(u) + s_a := a >> (128 - 1) // -1 if negative or 0 + s_b := b >> (128 - 1) + an := (a ~ s_a) - s_a // absolute + bn := (b ~ s_b) - s_b + + s_a ~= s_b // quotient sign + u_s_a := u128(s_a) + + return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative } diff --git a/base/runtime/os_specific_linux.odin b/base/runtime/os_specific_linux.odin index a944ba309..146e647fb 100644 --- a/base/runtime/os_specific_linux.odin +++ b/base/runtime/os_specific_linux.odin @@ -12,6 +12,8 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { SYS_write :: uintptr(4) } else when ODIN_ARCH == .arm32 { SYS_write :: uintptr(4) + } else when ODIN_ARCH == .riscv64 { + SYS_write :: uintptr(64) } stderr :: 2 diff --git a/base/runtime/procs_wasm.odin b/base/runtime/procs_wasm.odin index 7501df460..9f2e9befc 100644 --- a/base/runtime/procs_wasm.odin +++ b/base/runtime/procs_wasm.odin @@ -52,3 +52,24 @@ udivti3 :: proc "c" (la, ha, lb, hb: u64) -> u128 { b.lo, b.hi = lb, hb return udivmodti4(a.all, b.all, nil) } + +@(link_name="__lshrti3", linkage="strong") +__lshrti3 :: proc "c" (la, ha: u64, b: u32) -> i128 { + bits :: size_of(u32)*8 + + input, result: ti_int + input.lo = la + input.hi = ha + + if b & bits != 0 { + result.hi = 0 + result.lo = input.hi >> (b - bits) + } else if b == 0 { + return input.all + } else { + result.hi = input.hi >> b + result.lo = (input.hi << (bits - b)) | (input.lo >> b) + } + + return result.all +} diff --git a/base/runtime/thread_management.odin b/base/runtime/thread_management.odin new file mode 100644 index 000000000..cabd4691c --- /dev/null +++ b/base/runtime/thread_management.odin @@ -0,0 +1,34 @@ +package runtime + +Thread_Local_Cleaner :: #type proc "odin" () + +@(private="file") +thread_local_cleaners: [8]Thread_Local_Cleaner + +// Add a procedure that will be run at the end of a thread for the purpose of +// deallocating state marked as `thread_local`. +// +// Intended to be called in an `init` procedure of a package with +// dynamically-allocated memory that is stored in `thread_local` variables. +add_thread_local_cleaner :: proc "contextless" (p: Thread_Local_Cleaner) { + for &v in thread_local_cleaners { + if v == nil { + v = p + return + } + } + panic_contextless("There are no more thread-local cleaner slots available.") +} + +// Run all of the thread-local cleaner procedures. +// +// Intended to be called by the internals of a threading API at the end of a +// thread's lifetime. +run_thread_local_cleaners :: proc "odin" () { + for p in thread_local_cleaners { + if p == nil { + break + } + p() + } +} diff --git a/build.bat b/build.bat index bddde49c8..b458c0c67 100644 --- a/build.bat +++ b/build.bat @@ -116,6 +116,9 @@ 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 -vet -strict-style -- Hellope World +rem Many non-compiler devs seem to run debug build but don't realize. +if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat release" if you want a faster, release mode compiler. + del *.obj > NUL 2> NUL :end_of_build \ No newline at end of file diff --git a/build_odin.sh b/build_odin.sh index 125b9335a..f283dc441 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -23,6 +23,14 @@ error() { exit 1 } +# Brew advises people not to add llvm to their $PATH, so try and use brew to find it. +if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then + if [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config" + elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config" + elif [ -n "$(command -v $(brew --prefix llvm@14)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@14)/bin/llvm-config" + fi +fi + if [ -z "$LLVM_CONFIG" ]; then # darwin, linux, openbsd if [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18" @@ -95,7 +103,7 @@ Linux) LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" # Copy libLLVM*.so into current directory for linking # NOTE: This is needed by the Linux release pipeline! - cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ + # cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN" ;; OpenBSD) @@ -144,12 +152,17 @@ build_odin() { } run_demo() { - ./odin run examples/demo -vet -strict-style -- Hellope World + if [ $# -eq 0 ] || [ "$1" = "debug" ]; then + ./odin run examples/demo -vet -strict-style -- Hellope World + fi } if [ $# -eq 0 ]; then build_odin debug run_demo + + : ${PROGRAM:=$0} + printf "\nDebug compiler built. Note: run \"$PROGRAM release\" or \"$PROGRAM release-native\" if you want a faster, release mode compiler.\n" elif [ $# -eq 1 ]; then case $1 in report) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index a7e9b1c64..f4d883353 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -144,6 +144,9 @@ buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) { } buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } b.last_read = .Invalid if offset < 0 { err = .Invalid_Offset @@ -246,10 +249,13 @@ buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io. } buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } b.last_read = .Invalid if uint(offset) >= len(b.buf) { - err = .Invalid_Offset + err = .EOF return } n = copy(p, b.buf[offset:]) @@ -310,6 +316,27 @@ buffer_unread_rune :: proc(b: ^Buffer) -> io.Error { return nil } +buffer_seek :: proc(b: ^Buffer, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { + abs: i64 + switch whence { + case .Start: + abs = offset + case .Current: + abs = i64(b.off) + offset + case .End: + abs = i64(len(b.buf)) + offset + case: + return 0, .Invalid_Whence + } + + abs_int := int(abs) + if abs_int < 0 { + return 0, .Invalid_Offset + } + b.last_read = .Invalid + b.off = abs_int + return abs, nil +} buffer_read_bytes :: proc(b: ^Buffer, delim: byte) -> (line: []byte, err: io.Error) { i := index_byte(b.buf[b.off:], delim) @@ -395,14 +422,17 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse return io._i64_err(buffer_write(b, p)) case .Write_At: return io._i64_err(buffer_write_at(b, p, int(offset))) + case .Seek: + n, err = buffer_seek(b, offset, whence) + return case .Size: - n = i64(buffer_capacity(b)) + n = i64(buffer_length(b)) return case .Destroy: buffer_destroy(b) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query}) } return 0, .Empty } diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 7cbf092ac..45eb44307 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -1,9 +1,38 @@ package bytes +import "base:intrinsics" import "core:mem" +import "core:simd" import "core:unicode" import "core:unicode/utf8" +when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") { + @(private) + SCANNER_INDICES_256 : simd.u8x32 : { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + } + @(private) + SCANNER_SENTINEL_MAX_256: simd.u8x32 : u8(0x00) + @(private) + SCANNER_SENTINEL_MIN_256: simd.u8x32 : u8(0xff) + @(private) + SIMD_REG_SIZE_256 :: 32 +} +@(private) +SCANNER_INDICES_128 : simd.u8x16 : { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, +} +@(private) +SCANNER_SENTINEL_MAX_128: simd.u8x16 : u8(0x00) +@(private) +SCANNER_SENTINEL_MIN_128: simd.u8x16 : u8(0xff) +@(private) +SIMD_REG_SIZE_128 :: 16 + clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> []byte { c := make([]byte, len(s), allocator, loc) copy(c, s) @@ -293,28 +322,277 @@ split_after_iterator :: proc(s: ^[]byte, sep: []byte) -> ([]byte, bool) { return _split_iterator(s, sep, len(sep)) } +/* +Scan a slice of bytes for a specific byte. -index_byte :: proc(s: []byte, c: byte) -> int { - for i := 0; i < len(s); i += 1 { +This procedure safely handles slices of any length, including empty slices. + +Inputs: +- data: A slice of bytes. +- c: The byte to search for. + +Returns: +- index: The index of the byte `c`, or -1 if it was not found. +*/ +index_byte :: proc(s: []byte, c: byte) -> (index: int) #no_bounds_check { + i, l := 0, len(s) + + // Guard against small strings. On modern systems, it is ALWAYS + // worth vectorizing assuming there is a hardware vector unit, and + // the data size is large enough. + if l < SIMD_REG_SIZE_128 { + for /**/; i < l; i += 1 { + if s[i] == c { + return i + } + } + return -1 + } + + c_vec: simd.u8x16 = c + when !simd.IS_EMULATED { + // Note: While this is something that could also logically take + // advantage of AVX512, the various downclocking and power + // consumption related woes make premature to have a dedicated + // code path. + when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") { + c_vec_256: simd.u8x32 = c + + s_vecs: [4]simd.u8x32 = --- + c_vecs: [4]simd.u8x32 = --- + m_vec: [4]u8 = --- + + // Scan 128-byte chunks, using 256-bit SIMD. + for nr_blocks := l / (4 * SIMD_REG_SIZE_256); nr_blocks > 0; nr_blocks -= 1 { + #unroll for j in 0..<4 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] | m_vec[2] | m_vec[3] > 0 { + #unroll for j in 0..<4 { + if m_vec[j] > 0 { + sel := simd.select(c_vecs[j], SCANNER_INDICES_256, SCANNER_SENTINEL_MIN_256) + off := simd.reduce_min(sel) + return i + j * SIMD_REG_SIZE_256 + int(off) + } + } + } + + i += 4 * SIMD_REG_SIZE_256 + } + + // Scan 64-byte chunks, using 256-bit SIMD. + for nr_blocks := (l - i) / (2 * SIMD_REG_SIZE_256); nr_blocks > 0; nr_blocks -= 1 { + #unroll for j in 0..<2 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] > 0 { + #unroll for j in 0..<2 { + if m_vec[j] > 0 { + sel := simd.select(c_vecs[j], SCANNER_INDICES_256, SCANNER_SENTINEL_MIN_256) + off := simd.reduce_min(sel) + return i + j * SIMD_REG_SIZE_256 + int(off) + } + } + } + + i += 2 * SIMD_REG_SIZE_256 + } + } else { + s_vecs: [4]simd.u8x16 = --- + c_vecs: [4]simd.u8x16 = --- + m_vecs: [4]u8 = --- + + // Scan 64-byte chunks, using 128-bit SIMD. + for nr_blocks := l / (4 * SIMD_REG_SIZE_128); nr_blocks > 0; nr_blocks -= 1 { + #unroll for j in 0..<4 { + s_vecs[j]= intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i+j*SIMD_REG_SIZE_128:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec) + m_vecs[j] = simd.reduce_or(c_vecs[j]) + } + if m_vecs[0] | m_vecs[1] | m_vecs[2] | m_vecs[3] > 0 { + #unroll for j in 0..<4 { + if m_vecs[j] > 0 { + sel := simd.select(c_vecs[j], SCANNER_INDICES_128, SCANNER_SENTINEL_MIN_128) + off := simd.reduce_min(sel) + return i + j * SIMD_REG_SIZE_128 + int(off) + } + } + } + + i += 4 * SIMD_REG_SIZE_128 + } + } + } + + // Scan the remaining SIMD register sized chunks. + // + // Apparently LLVM does ok with 128-bit SWAR, so this path is also taken + // on potato targets. Scanning more at a time when LLVM is emulating SIMD + // likely does not buy much, as all that does is increase GP register + // pressure. + for nr_blocks := (l - i) / SIMD_REG_SIZE_128; nr_blocks > 0; nr_blocks -= 1 { + s0 := intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i:])) + c0 := simd.lanes_eq(s0, c_vec) + if simd.reduce_or(c0) > 0 { + sel := simd.select(c0, SCANNER_INDICES_128, SCANNER_SENTINEL_MIN_128) + off := simd.reduce_min(sel) + return i + int(off) + } + + i += SIMD_REG_SIZE_128 + } + + // Scan serially for the remainder. + for /**/; i < l; i += 1 { if s[i] == c { return i } } + return -1 } -// Returns -1 if c is not present -last_index_byte :: proc(s: []byte, c: byte) -> int { - for i := len(s)-1; i >= 0; i -= 1 { +/* +Scan a slice of bytes for a specific byte, starting from the end and working +backwards to the start. + +This procedure safely handles slices of any length, including empty slices. + +Inputs: +- data: A slice of bytes. +- c: The byte to search for. + +Returns: +- index: The index of the byte `c`, or -1 if it was not found. +*/ +last_index_byte :: proc(s: []byte, c: byte) -> int #no_bounds_check { + i := len(s) + + // Guard against small strings. On modern systems, it is ALWAYS + // worth vectorizing assuming there is a hardware vector unit, and + // the data size is large enough. + if i < SIMD_REG_SIZE_128 { + #reverse for ch, j in s { + if ch == c { + return j + } + } + return -1 + } + + c_vec: simd.u8x16 = c + when !simd.IS_EMULATED { + // Note: While this is something that could also logically take + // advantage of AVX512, the various downclocking and power + // consumption related woes make premature to have a dedicated + // code path. + when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") { + c_vec_256: simd.u8x32 = c + + s_vecs: [4]simd.u8x32 = --- + c_vecs: [4]simd.u8x32 = --- + m_vec: [4]u8 = --- + + // Scan 128-byte chunks, using 256-bit SIMD. + for i >= 4 * SIMD_REG_SIZE_256 { + i -= 4 * SIMD_REG_SIZE_256 + + #unroll for j in 0..<4 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] | m_vec[2] | m_vec[3] > 0 { + #unroll for j in 0..<4 { + if m_vec[3-j] > 0 { + sel := simd.select(c_vecs[3-j], SCANNER_INDICES_256, SCANNER_SENTINEL_MAX_256) + off := simd.reduce_max(sel) + return i + (3-j) * SIMD_REG_SIZE_256 + int(off) + } + } + } + } + + // Scan 64-byte chunks, using 256-bit SIMD. + for i >= 2 * SIMD_REG_SIZE_256 { + i -= 2 * SIMD_REG_SIZE_256 + + #unroll for j in 0..<2 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] > 0 { + #unroll for j in 0..<2 { + if m_vec[1-j] > 0 { + sel := simd.select(c_vecs[1-j], SCANNER_INDICES_256, SCANNER_SENTINEL_MAX_256) + off := simd.reduce_max(sel) + return i + (1-j) * SIMD_REG_SIZE_256 + int(off) + } + } + } + } + } else { + s_vecs: [4]simd.u8x16 = --- + c_vecs: [4]simd.u8x16 = --- + m_vecs: [4]u8 = --- + + // Scan 64-byte chunks, using 128-bit SIMD. + for i >= 4 * SIMD_REG_SIZE_128 { + i -= 4 * SIMD_REG_SIZE_128 + + #unroll for j in 0..<4 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i+j*SIMD_REG_SIZE_128:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec) + m_vecs[j] = simd.reduce_or(c_vecs[j]) + } + if m_vecs[0] | m_vecs[1] | m_vecs[2] | m_vecs[3] > 0 { + #unroll for j in 0..<4 { + if m_vecs[3-j] > 0 { + sel := simd.select(c_vecs[3-j], SCANNER_INDICES_128, SCANNER_SENTINEL_MAX_128) + off := simd.reduce_max(sel) + return i + (3-j) * SIMD_REG_SIZE_128 + int(off) + } + } + } + } + } + } + + // Scan the remaining SIMD register sized chunks. + // + // Apparently LLVM does ok with 128-bit SWAR, so this path is also taken + // on potato targets. Scanning more at a time when LLVM is emulating SIMD + // likely does not buy much, as all that does is increase GP register + // pressure. + for i >= SIMD_REG_SIZE_128 { + i -= SIMD_REG_SIZE_128 + + s0 := intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i:])) + c0 := simd.lanes_eq(s0, c_vec) + if simd.reduce_or(c0) > 0 { + sel := simd.select(c0, SCANNER_INDICES_128, SCANNER_SENTINEL_MAX_128) + off := simd.reduce_max(sel) + return i + int(off) + } + } + + // Scan serially for the remainder. + for i > 0 { + i -= 1 if s[i] == c { return i } } + return -1 } - @private PRIME_RABIN_KARP :: 16777619 index :: proc(s, substr: []byte) -> int { diff --git a/core/bytes/reader.odin b/core/bytes/reader.odin index 4b18345ba..2e1c5ed42 100644 --- a/core/bytes/reader.odin +++ b/core/bytes/reader.odin @@ -9,10 +9,11 @@ Reader :: struct { prev_rune: int, // previous reading index of rune or < 0 } -reader_init :: proc(r: ^Reader, s: []byte) { +reader_init :: proc(r: ^Reader, s: []byte) -> io.Stream { r.s = s r.i = 0 r.prev_rune = -1 + return reader_to_stream(r) } reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) { @@ -33,6 +34,9 @@ reader_size :: proc(r: ^Reader) -> i64 { } reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if r.i >= i64(len(r.s)) { return 0, .EOF } @@ -42,6 +46,9 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { return } reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if off < 0 { return 0, .Invalid_Offset } @@ -97,7 +104,6 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error { return nil } reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - r.prev_rune = -1 abs: i64 switch whence { case .Start: @@ -114,6 +120,7 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E return 0, .Invalid_Offset } r.i = abs + r.prev_rune = -1 return abs, nil } reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { diff --git a/core/c/libc/complex.odin b/core/c/libc/complex.odin index 81d2b75be..98fd7b1bb 100644 --- a/core/c/libc/complex.odin +++ b/core/c/libc/complex.odin @@ -47,8 +47,8 @@ foreign libc { clogf :: proc(z: complex_float) -> complex_float --- // 7.3.8 Power and absolute-value functions - cabs :: proc(z: complex_double) -> complex_double --- - cabsf :: proc(z: complex_float) -> complex_float --- + cabs :: proc(z: complex_double) -> double --- + cabsf :: proc(z: complex_float) -> float --- cpow :: proc(x, y: complex_double) -> complex_double --- cpowf :: proc(x, y: complex_float) -> complex_float --- csqrt :: proc(z: complex_double) -> complex_double --- diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index d28a24f56..843b2f1b6 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -102,6 +102,6 @@ when ODIN_OS == .Haiku { // read the value, or to produce an lvalue such that you can assign a different // error value to errno. To work around this, just expose it as a function like // it actually is. -errno :: #force_inline proc() -> ^int { +errno :: #force_inline proc "contextless" () -> ^int { return _get_errno() } diff --git a/core/c/libc/setjmp.odin b/core/c/libc/setjmp.odin index 68f5ac010..101b614b3 100644 --- a/core/c/libc/setjmp.odin +++ b/core/c/libc/setjmp.odin @@ -32,24 +32,21 @@ when ODIN_OS == .Windows { // the RDX register will contain zero and correctly set the flag to disable // stack unwinding. @(link_name="_setjmp") - setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int --- + setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int --- } } else { @(default_calling_convention="c") foreign libc { // 7.13.1 Save calling environment - // - // NOTE(dweiler): C11 requires setjmp be a macro, which means it won't - // necessarily export a symbol named setjmp but rather _setjmp in the case - // of musl, glibc, BSD libc, and msvcrt. - @(link_name="_setjmp") - setjmp :: proc(env: ^jmp_buf) -> int --- + @(link_name=LSETJMP) + setjmp :: proc(env: ^jmp_buf) -> int --- } } @(default_calling_convention="c") foreign libc { // 7.13.2 Restore calling environment + @(link_name=LLONGJMP) longjmp :: proc(env: ^jmp_buf, val: int) -> ! --- } @@ -64,3 +61,11 @@ foreign libc { // The choice of 4096 bytes for storage of this type is more than enough on all // relevant platforms. jmp_buf :: struct #align(16) { _: [4096]char, } + +when ODIN_OS == .NetBSD { + @(private) LSETJMP :: "__setjmp14" + @(private) LLONGJMP :: "__longjmp14" +} else { + @(private) LSETJMP :: "setjmp" + @(private) LLONGJMP :: "longjmp" +} diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 3e1d0f5a2..019389b0d 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -17,6 +17,12 @@ when ODIN_OS == .Windows { FILE :: struct {} +Whence :: enum int { + SET = SEEK_SET, + CUR = SEEK_CUR, + END = SEEK_END, +} + // MSVCRT compatible. when ODIN_OS == .Windows { _IOFBF :: 0x0000 @@ -101,6 +107,8 @@ when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { SEEK_CUR :: 1 SEEK_END :: 2 + TMP_MAX :: 308915776 + foreign libc { __sF: [3]FILE } @@ -128,6 +136,8 @@ when ODIN_OS == .FreeBSD { SEEK_CUR :: 1 SEEK_END :: 2 + TMP_MAX :: 308915776 + foreign libc { @(link_name="__stderrp") stderr: ^FILE @(link_name="__stdinp") stdin: ^FILE @@ -195,10 +205,21 @@ when ODIN_OS == .Haiku { } } +when ODIN_OS == .NetBSD { + @(private) LRENAME :: "__posix_rename" + @(private) LFGETPOS :: "__fgetpos50" + @(private) LFSETPOS :: "__fsetpos50" +} else { + @(private) LRENAME :: "rename" + @(private) LFGETPOS :: "fgetpos" + @(private) LFSETPOS :: "fsetpos" +} + @(default_calling_convention="c") foreign libc { // 7.21.4 Operations on files remove :: proc(filename: cstring) -> int --- + @(link_name=LRENAME) rename :: proc(old, new: cstring) -> int --- tmpfile :: proc() -> ^FILE --- tmpnam :: proc(s: [^]char) -> [^]char --- @@ -240,8 +261,10 @@ foreign libc { fwrite :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t --- // 7.21.9 File positioning functions + @(link_name=LFGETPOS) fgetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int --- - fseek :: proc(stream: ^FILE, offset: long, whence: int) -> int --- + fseek :: proc(stream: ^FILE, offset: long, whence: Whence) -> int --- + @(link_name=LFSETPOS) fsetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int --- ftell :: proc(stream: ^FILE) -> long --- rewind :: proc(stream: ^FILE) --- @@ -288,11 +311,11 @@ to_stream :: proc(file: ^FILE) -> io.Stream { return 0, unknown_or_eof(file) } - if fseek(file, long(offset), SEEK_SET) != 0 { + if fseek(file, long(offset), .SET) != 0 { return 0, unknown_or_eof(file) } - defer fseek(file, long(curr), SEEK_SET) + defer fseek(file, long(curr), .SET) n = i64(fread(raw_data(p), size_of(byte), len(p), file)) if n == 0 { err = unknown_or_eof(file) } @@ -307,17 +330,21 @@ to_stream :: proc(file: ^FILE) -> io.Stream { return 0, unknown_or_eof(file) } - if fseek(file, long(offset), SEEK_SET) != 0 { + if fseek(file, long(offset), .SET) != 0 { return 0, unknown_or_eof(file) } - defer fseek(file, long(curr), SEEK_SET) + defer fseek(file, long(curr), .SET) n = i64(fwrite(raw_data(p), size_of(byte), len(p), file)) if n == 0 { err = unknown_or_eof(file) } case .Seek: - if fseek(file, long(offset), int(whence)) != 0 { + #assert(int(Whence.SET) == int(io.Seek_From.Start)) + #assert(int(Whence.CUR) == int(io.Seek_From.Current)) + #assert(int(Whence.END) == int(io.Seek_From.End)) + + if fseek(file, long(offset), Whence(whence)) != 0 { return 0, unknown_or_eof(file) } @@ -326,9 +353,9 @@ to_stream :: proc(file: ^FILE) -> io.Stream { if curr == -1 { return 0, unknown_or_eof(file) } - defer fseek(file, curr, SEEK_SET) + defer fseek(file, curr, .SET) - if fseek(file, 0, SEEK_END) != 0 { + if fseek(file, 0, .END) != 0 { return 0, unknown_or_eof(file) } @@ -341,7 +368,7 @@ to_stream :: proc(file: ^FILE) -> io.Stream { return 0, .Empty case .Query: - return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size }) + return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query }) } return } diff --git a/core/c/libc/stdlib.odin b/core/c/libc/stdlib.odin index d797b8746..08c6fa6f0 100644 --- a/core/c/libc/stdlib.odin +++ b/core/c/libc/stdlib.odin @@ -40,10 +40,9 @@ when ODIN_OS == .Linux { } -when ODIN_OS == .Darwin { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { RAND_MAX :: 0x7fffffff - // GLIBC and MUSL only @(private="file") @(default_calling_convention="c") foreign libc { @@ -55,6 +54,20 @@ when ODIN_OS == .Darwin { } } +when ODIN_OS == .NetBSD { + RAND_MAX :: 0x7fffffff + + @(private="file") + @(default_calling_convention="c") + foreign libc { + __mb_cur_max: size_t + } + + MB_CUR_MAX :: #force_inline proc() -> size_t { + return __mb_cur_max + } +} + // C does not declare what these values should be, as an implementation is free // to use any two distinct values it wants to indicate success or failure. // However, nobody actually does and everyone appears to have agreed upon these @@ -99,7 +112,7 @@ foreign libc { at_quick_exit :: proc(func: proc "c" ()) -> int --- exit :: proc(status: int) -> ! --- _Exit :: proc(status: int) -> ! --- - getenv :: proc(name: cstring) -> [^]char --- + getenv :: proc(name: cstring) -> cstring --- quick_exit :: proc(status: int) -> ! --- system :: proc(cmd: cstring) -> int --- @@ -150,4 +163,4 @@ aligned_free :: #force_inline proc "c" (ptr: rawptr) { } else { free(ptr) } -} \ No newline at end of file +} diff --git a/core/c/libc/string.odin b/core/c/libc/string.odin index e6a959f7b..cde9c7e6b 100644 --- a/core/c/libc/string.odin +++ b/core/c/libc/string.odin @@ -40,7 +40,7 @@ foreign libc { strtok :: proc(s1: [^]char, s2: cstring) -> [^]char --- // 7.24.6 Miscellaneous functions - strerror :: proc(errnum: int) -> [^]char --- + strerror :: proc(errnum: int) -> cstring --- strlen :: proc(s: cstring) -> size_t --- } memset :: proc "c" (s: rawptr, c: int, n: size_t) -> rawptr { diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 924cf4aec..48def707e 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -50,30 +50,56 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS = foreign libc { // 7.27.2 Time manipulation functions clock :: proc() -> clock_t --- + @(link_name=LDIFFTIME) difftime :: proc(time1, time2: time_t) -> double --- + @(link_name=LMKTIME) mktime :: proc(timeptr: ^tm) -> time_t --- + @(link_name=LTIME) time :: proc(timer: ^time_t) -> time_t --- timespec_get :: proc(ts: ^timespec, base: int) -> int --- // 7.27.3 Time conversion functions asctime :: proc(timeptr: ^tm) -> [^]char --- + @(link_name=LCTIME) ctime :: proc(timer: ^time_t) -> [^]char --- + @(link_name=LGMTIME) gmtime :: proc(timer: ^time_t) -> ^tm --- + @(link_name=LLOCALTIME) localtime :: proc(timer: ^time_t) -> ^tm --- strftime :: proc(s: [^]char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t --- } + when ODIN_OS == .NetBSD { + @(private) LDIFFTIME :: "__difftime50" + @(private) LMKTIME :: "__mktime50" + @(private) LTIME :: "__time50" + @(private) LCTIME :: "__ctime50" + @(private) LGMTIME :: "__gmtime50" + @(private) LLOCALTIME :: "__localtime50" + } else { + @(private) LDIFFTIME :: "difftime" + @(private) LMKTIME :: "mktime" + @(private) LTIME :: "time" + @(private) LCTIME :: "ctime" + @(private) LGMTIME :: "gmtime" + @(private) LLOCALTIME :: "localtime" + } + when ODIN_OS == .OpenBSD { CLOCKS_PER_SEC :: 100 } else { CLOCKS_PER_SEC :: 1000000 } - TIME_UTC :: 1 + TIME_UTC :: 1 - time_t :: distinct i64 + time_t :: distinct i64 - clock_t :: long + when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + clock_t :: distinct int32_t + } else { + clock_t :: distinct long + } timespec :: struct { tv_sec: time_t, diff --git a/core/compress/gzip/doc.odin b/core/compress/gzip/doc.odin new file mode 100644 index 000000000..fd7ef5a19 --- /dev/null +++ b/core/compress/gzip/doc.odin @@ -0,0 +1,90 @@ +/* + Copyright 2021 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + Ginger Bill: Cosmetic changes. + + A small GZIP implementation as an example. +*/ + +/* +Example: + import "core:bytes" + import "core:os" + import "core:compress" + import "core:fmt" + + // Small GZIP file with fextra, fname and fcomment present. + @private + TEST: []u8 = { + 0x1f, 0x8b, 0x08, 0x1c, 0xcb, 0x3b, 0x3a, 0x5a, + 0x02, 0x03, 0x07, 0x00, 0x61, 0x62, 0x03, 0x00, + 0x63, 0x64, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x00, 0x54, 0x68, 0x69, 0x73, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x2b, 0x48, + 0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x01, 0x00, 0x15, + 0x6a, 0x2c, 0x42, 0x07, 0x00, 0x00, 0x00, + } + + main :: proc() { + // Set up output buffer. + buf := bytes.Buffer{} + + stdout :: proc(s: string) { + os.write_string(os.stdout, s) + } + stderr :: proc(s: string) { + os.write_string(os.stderr, s) + } + + args := os.args + + if len(args) < 2 { + stderr("No input file specified.\n") + err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) + if err == nil { + stdout("Displaying test vector: ") + stdout(bytes.buffer_to_string(&buf)) + stdout("\n") + } else { + fmt.printf("gzip.load returned %v\n", err) + } + bytes.buffer_destroy(&buf) + os.exit(0) + } + + // The rest are all files. + args = args[1:] + err: Error + + for file in args { + if file == "-" { + // Read from stdin + s := os.stream_from_handle(os.stdin) + ctx := &compress.Context_Stream_Input{ + input = s, + } + err = load(ctx, &buf) + } else { + err = load(file, &buf) + } + if err != nil { + if err != E_General.File_Not_Found { + stderr("File not found: ") + stderr(file) + stderr("\n") + os.exit(1) + } + stderr("GZIP returned an error.\n") + bytes.buffer_destroy(&buf) + os.exit(2) + } + stdout(bytes.buffer_to_string(&buf)) + } + bytes.buffer_destroy(&buf) + } +*/ +package compress_gzip diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin deleted file mode 100644 index 09540aafc..000000000 --- a/core/compress/gzip/example.odin +++ /dev/null @@ -1,89 +0,0 @@ -//+build ignore -package compress_gzip - -/* - Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - Ginger Bill: Cosmetic changes. - - A small GZIP implementation as an example. -*/ - -import "core:bytes" -import "core:os" -import "core:compress" -import "core:fmt" - -// Small GZIP file with fextra, fname and fcomment present. -@private -TEST: []u8 = { - 0x1f, 0x8b, 0x08, 0x1c, 0xcb, 0x3b, 0x3a, 0x5a, - 0x02, 0x03, 0x07, 0x00, 0x61, 0x62, 0x03, 0x00, - 0x63, 0x64, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x6e, - 0x61, 0x6d, 0x65, 0x00, 0x54, 0x68, 0x69, 0x73, - 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x2b, 0x48, - 0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x01, 0x00, 0x15, - 0x6a, 0x2c, 0x42, 0x07, 0x00, 0x00, 0x00, -} - -main :: proc() { - // Set up output buffer. - buf := bytes.Buffer{} - - stdout :: proc(s: string) { - os.write_string(os.stdout, s) - } - stderr :: proc(s: string) { - os.write_string(os.stderr, s) - } - - args := os.args - - if len(args) < 2 { - stderr("No input file specified.\n") - err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) - if err == nil { - stdout("Displaying test vector: ") - stdout(bytes.buffer_to_string(&buf)) - stdout("\n") - } else { - fmt.printf("gzip.load returned %v\n", err) - } - bytes.buffer_destroy(&buf) - os.exit(0) - } - - // The rest are all files. - args = args[1:] - err: Error - - for file in args { - if file == "-" { - // Read from stdin - s := os.stream_from_handle(os.stdin) - ctx := &compress.Context_Stream_Input{ - input = s, - } - err = load(ctx, &buf) - } else { - err = load(file, &buf) - } - if err != nil { - if err != E_General.File_Not_Found { - stderr("File not found: ") - stderr(file) - stderr("\n") - os.exit(1) - } - stderr("GZIP returned an error.\n") - bytes.buffer_destroy(&buf) - os.exit(2) - } - stdout(bytes.buffer_to_string(&buf)) - } - bytes.buffer_destroy(&buf) -} diff --git a/core/compress/shoco/model.odin b/core/compress/shoco/model.odin index f62236c00..919563441 100644 --- a/core/compress/shoco/model.odin +++ b/core/compress/shoco/model.odin @@ -4,7 +4,6 @@ which is an English word model. */ -// package shoco is an implementation of the shoco short string compressor package compress_shoco DEFAULT_MODEL :: Shoco_Model { @@ -145,4 +144,4 @@ DEFAULT_MODEL :: Shoco_Model { { 0xc0000000, 2, 4, { 25, 22, 19, 16, 16, 16, 16, 16 }, { 15, 7, 7, 7, 0, 0, 0, 0 }, 0xe0, 0xc0 }, { 0xe0000000, 4, 8, { 23, 19, 15, 11, 8, 5, 2, 0 }, { 31, 15, 15, 15, 7, 7, 7, 3 }, 0xf0, 0xe0 }, }, -} \ No newline at end of file +} diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin index 269dd8875..b393b8356 100644 --- a/core/compress/shoco/shoco.odin +++ b/core/compress/shoco/shoco.odin @@ -8,7 +8,7 @@ An implementation of [shoco](https://github.com/Ed-von-Schleck/shoco) by Christian Schramm. */ -// package shoco is an implementation of the shoco short string compressor +// package shoco is an implementation of the shoco short string compressor. package compress_shoco import "base:intrinsics" @@ -308,4 +308,4 @@ compress_string :: proc(input: string, model := DEFAULT_MODEL, allocator := cont resize(&buf, length) or_return return buf[:length], result } -compress :: proc{compress_string_to_buffer, compress_string} \ No newline at end of file +compress :: proc{compress_string_to_buffer, compress_string} diff --git a/core/compress/zlib/doc.odin b/core/compress/zlib/doc.odin new file mode 100644 index 000000000..0a5b4eb40 --- /dev/null +++ b/core/compress/zlib/doc.odin @@ -0,0 +1,50 @@ +/* + Copyright 2021 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + + An example of how to use `zlib.inflate`. +*/ + +/* +Example: + package main + + import "core:bytes" + import "core:fmt" + + main :: proc() { + ODIN_DEMO := []u8{ + 120, 218, 101, 144, 65, 110, 131, 48, 16, 69, 215, 246, 41, 190, 44, 69, 73, 32, 148, 182, + 75, 75, 28, 32, 251, 46, 217, 88, 238, 0, 86, 192, 32, 219, 36, 170, 170, 172, 122, 137, + 238, 122, 197, 30, 161, 70, 162, 20, 81, 203, 139, 25, 191, 255, 191, 60, 51, 40, 125, 81, + 53, 33, 144, 15, 156, 155, 110, 232, 93, 128, 208, 189, 35, 89, 117, 65, 112, 222, 41, 99, + 33, 37, 6, 215, 235, 195, 17, 239, 156, 197, 170, 118, 170, 131, 44, 32, 82, 164, 72, 240, + 253, 245, 249, 129, 12, 185, 224, 76, 105, 61, 118, 99, 171, 66, 239, 38, 193, 35, 103, 85, + 172, 66, 127, 33, 139, 24, 244, 235, 141, 49, 204, 223, 76, 208, 205, 204, 166, 7, 173, 60, + 97, 159, 238, 37, 214, 41, 105, 129, 167, 5, 102, 27, 152, 173, 97, 178, 129, 73, 129, 231, + 5, 230, 27, 152, 175, 225, 52, 192, 127, 243, 170, 157, 149, 18, 121, 142, 115, 109, 227, 122, + 64, 87, 114, 111, 161, 49, 182, 6, 181, 158, 162, 226, 206, 167, 27, 215, 246, 48, 56, 99, + 67, 117, 16, 47, 13, 45, 35, 151, 98, 231, 75, 1, 173, 90, 61, 101, 146, 71, 136, 244, + 170, 218, 145, 176, 123, 45, 173, 56, 113, 134, 191, 51, 219, 78, 235, 95, 28, 249, 253, 7, + 159, 150, 133, 125, + } + OUTPUT_SIZE :: 432 + + buf: bytes.Buffer + + // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. + err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) + defer bytes.buffer_destroy(&buf) + + if err != nil { + fmt.printf("\nError: %v\n", err) + } + s := bytes.buffer_to_string(&buf) + fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s) + assert(len(s) == OUTPUT_SIZE) + } +*/ +package compress_zlib diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin deleted file mode 100644 index fedd6671d..000000000 --- a/core/compress/zlib/example.odin +++ /dev/null @@ -1,47 +0,0 @@ -//+build ignore -package compress_zlib - -/* - Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - - An example of how to use `zlib.inflate`. -*/ - -import "core:bytes" -import "core:fmt" - -main :: proc() { - ODIN_DEMO := []u8{ - 120, 218, 101, 144, 65, 110, 131, 48, 16, 69, 215, 246, 41, 190, 44, 69, 73, 32, 148, 182, - 75, 75, 28, 32, 251, 46, 217, 88, 238, 0, 86, 192, 32, 219, 36, 170, 170, 172, 122, 137, - 238, 122, 197, 30, 161, 70, 162, 20, 81, 203, 139, 25, 191, 255, 191, 60, 51, 40, 125, 81, - 53, 33, 144, 15, 156, 155, 110, 232, 93, 128, 208, 189, 35, 89, 117, 65, 112, 222, 41, 99, - 33, 37, 6, 215, 235, 195, 17, 239, 156, 197, 170, 118, 170, 131, 44, 32, 82, 164, 72, 240, - 253, 245, 249, 129, 12, 185, 224, 76, 105, 61, 118, 99, 171, 66, 239, 38, 193, 35, 103, 85, - 172, 66, 127, 33, 139, 24, 244, 235, 141, 49, 204, 223, 76, 208, 205, 204, 166, 7, 173, 60, - 97, 159, 238, 37, 214, 41, 105, 129, 167, 5, 102, 27, 152, 173, 97, 178, 129, 73, 129, 231, - 5, 230, 27, 152, 175, 225, 52, 192, 127, 243, 170, 157, 149, 18, 121, 142, 115, 109, 227, 122, - 64, 87, 114, 111, 161, 49, 182, 6, 181, 158, 162, 226, 206, 167, 27, 215, 246, 48, 56, 99, - 67, 117, 16, 47, 13, 45, 35, 151, 98, 231, 75, 1, 173, 90, 61, 101, 146, 71, 136, 244, - 170, 218, 145, 176, 123, 45, 173, 56, 113, 134, 191, 51, 219, 78, 235, 95, 28, 249, 253, 7, - 159, 150, 133, 125, - } - OUTPUT_SIZE :: 432 - - buf: bytes.Buffer - - // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. - err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) - defer bytes.buffer_destroy(&buf) - - if err != nil { - fmt.printf("\nError: %v\n", err) - } - s := bytes.buffer_to_string(&buf) - fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s) - assert(len(s) == OUTPUT_SIZE) -} diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index c7ae9e9c8..2dc9e81df 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -12,6 +12,7 @@ package compress_zlib import "core:compress" +import "base:intrinsics" import "core:mem" import "core:io" import "core:hash" @@ -123,13 +124,7 @@ Huffman_Table :: struct { @(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 - // by reversing all of the bits and masking out the unneeded ones. - r = n - r = ((r & 0xAAAA) >> 1) | ((r & 0x5555) << 1) - r = ((r & 0xCCCC) >> 2) | ((r & 0x3333) << 2) - r = ((r & 0xF0F0) >> 4) | ((r & 0x0F0F) << 4) - r = ((r & 0xFF00) >> 8) | ((r & 0x00FF) << 8) + r = intrinsics.reverse_bits(n) r >>= (16 - bits) return diff --git a/core/container/bit_array/bit_array.odin b/core/container/bit_array/bit_array.odin index b53bacda7..9a76dc78f 100644 --- a/core/container/bit_array/bit_array.odin +++ b/core/container/bit_array/bit_array.odin @@ -1,5 +1,6 @@ package container_dynamic_bit_array +import "base:builtin" import "base:intrinsics" import "core:mem" @@ -18,7 +19,7 @@ NUM_BITS :: 64 Bit_Array :: struct { bits: [dynamic]u64, bias: int, - max_index: int, + length: int, free_pointer: bool, } @@ -52,9 +53,9 @@ Returns: */ iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok: bool) { index = it.word_idx * NUM_BITS + int(it.bit_idx) + it.array.bias - if index > it.array.max_index { return false, 0, false } + if index >= it.array.length + it.array.bias { return false, 0, false } - word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 set = (word >> it.bit_idx & 1) == 1 it.bit_idx += 1 @@ -106,22 +107,22 @@ Returns: */ @(private="file") iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) { - word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 when ! ITERATE_SET_BITS { word = ~word } // If the word is empty or we have already gone over all the bits in it, // b.bit_idx is greater than the index of any set bit in the word, // meaning that word >> b.bit_idx == 0. - for it.word_idx < len(it.array.bits) && word >> it.bit_idx == 0 { + for it.word_idx < builtin.len(it.array.bits) && word >> it.bit_idx == 0 { it.word_idx += 1 it.bit_idx = 0 - word = it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word = it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 when ! ITERATE_SET_BITS { word = ~word } } // If we are iterating the set bits, reaching the end of the array means we have no more bits to check when ITERATE_SET_BITS { - if it.word_idx >= len(it.array.bits) { + if it.word_idx >= builtin.len(it.array.bits) { return 0, false } } @@ -135,7 +136,7 @@ iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> it.bit_idx = 0 it.word_idx += 1 } - return index, index <= it.array.max_index + return index, index < it.array.length + it.array.bias } /* Gets the state of a bit in the bit-array @@ -160,7 +161,7 @@ get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #opti If we `get` a bit that doesn't fit in the Bit Array, it's naturally `false`. This early-out prevents unnecessary resizing. */ - if leg_index + 1 > len(ba.bits) { return false, true } + if leg_index + 1 > builtin.len(ba.bits) { return false, true } val := u64(1 << uint(bit_index)) res = ba.bits[leg_index] & val == val @@ -208,7 +209,7 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator resize_if_needed(ba, leg_index) or_return - ba.max_index = max(idx, ba.max_index) + ba.length = max(1 + idx, ba.length) if set_to { ba.bits[leg_index] |= 1 << uint(bit_index) @@ -261,6 +262,9 @@ unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check { /* A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative). +The range of bits created by this procedure is `min_index..> INDEX_SHIFT if size_in_bits & INDEX_MASK > 0 {legs+=1} @@ -284,7 +288,7 @@ create :: proc(max_index: int, min_index: int = 0, allocator := context.allocato res = new(Bit_Array) res.bits = bits res.bias = min_index - res.max_index = max_index + res.length = max_index - min_index res.free_pointer = true return } @@ -299,6 +303,48 @@ clear :: proc(ba: ^Bit_Array) { mem.zero_slice(ba.bits[:]) } /* +Gets the length of set and unset valid bits in the Bit_Array. + +Inputs: +- ba: The target Bit_Array + +Returns: +- length: The length of valid bits. +*/ +len :: proc(ba: ^Bit_Array) -> (length: int) { + if ba == nil { return } + return ba.length +} +/* +Shrinks the Bit_Array's backing storage to the smallest possible size. + +Inputs: +- ba: The target Bit_Array +*/ +shrink :: proc(ba: ^Bit_Array) #no_bounds_check { + if ba == nil { return } + legs_needed := builtin.len(ba.bits) + for i := legs_needed - 1; i >= 0; i -= 1 { + if ba.bits[i] == 0 { + legs_needed -= 1 + } else { + break + } + } + if legs_needed == builtin.len(ba.bits) { + return + } + ba.length = 0 + if legs_needed > 0 { + if legs_needed > 1 { + ba.length = (legs_needed - 1) * NUM_BITS + } + ba.length += NUM_BITS - int(intrinsics.count_leading_zeros(ba.bits[legs_needed - 1])) + } + resize(&ba.bits, legs_needed) + builtin.shrink(&ba.bits) +} +/* Deallocates the Bit_Array and its backing storage Inputs: @@ -321,8 +367,8 @@ resize_if_needed :: proc(ba: ^Bit_Array, legs: int, allocator := context.allocat context.allocator = allocator - if legs + 1 > len(ba.bits) { + if legs + 1 > builtin.len(ba.bits) { resize(&ba.bits, legs + 1) } - return len(ba.bits) > legs + return builtin.len(ba.bits) > legs } diff --git a/core/container/bit_array/doc.odin b/core/container/bit_array/doc.odin index 77e1904a8..36bf90002 100644 --- a/core/container/bit_array/doc.odin +++ b/core/container/bit_array/doc.odin @@ -1,8 +1,8 @@ /* The Bit Array can be used in several ways: -- By default you don't need to instantiate a Bit Array: - +By default you don't need to instantiate a Bit Array. +Example: package test import "core:fmt" @@ -22,8 +22,8 @@ The Bit Array can be used in several ways: destroy(&bits) } -- A Bit Array can optionally allow for negative indices, if the minimum value was given during creation: - +A Bit Array can optionally allow for negative indices, if the minimum value was given during creation. +Example: package test import "core:fmt" diff --git a/core/container/intrusive/list/doc.odin b/core/container/intrusive/list/doc.odin index 1a5a12f49..155f1dfe2 100644 --- a/core/container/intrusive/list/doc.odin +++ b/core/container/intrusive/list/doc.odin @@ -1,22 +1,22 @@ /* Package list implements an intrusive doubly-linked list. -An intrusive container requires a `Node` to be embedded in your own structure, like this: - +An intrusive container requires a `Node` to be embedded in your own structure, like this. +Example: 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: - +Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed. +Example: My_String :: struct { using node: list.Node, value: string, } -Here is a full example: - +Here is a full example. +Example: package test import "core:fmt" @@ -42,5 +42,8 @@ Here is a full example: value: string, } +Output: + Hello + World */ package container_intrusive_list diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index b2068469d..77bb21cbc 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -139,9 +139,13 @@ clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { resize(a, 0) } -push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) { - n := copy(a.data[a.len:], items[:]) - a.len += n +push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool { + if a.len + builtin.len(items) <= cap(a^) { + n := copy(a.data[a.len:], items[:]) + a.len += n + return true + } + return false } inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check { diff --git a/core/crypto/_aes/ct64/ghash.odin b/core/crypto/_aes/ct64/ghash.odin index 21ac2ca97..a522a481a 100644 --- a/core/crypto/_aes/ct64/ghash.odin +++ b/core/crypto/_aes/ct64/ghash.odin @@ -80,8 +80,8 @@ ghash :: proc "contextless" (dst, key, data: []byte) { h2 := h0 ~ h1 h2r := h0r ~ h1r - src: []byte for l > 0 { + src: []byte = --- if l >= _aes.GHASH_BLOCK_SIZE { src = buf buf = buf[_aes.GHASH_BLOCK_SIZE:] diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin index 5cb5a68bb..1796bb093 100644 --- a/core/crypto/_aes/hw_intel/api.odin +++ b/core/crypto/_aes/hw_intel/api.odin @@ -3,7 +3,7 @@ package aes_hw_intel import "core:sys/info" -// is_supporte returns true iff hardware accelerated AES +// is_supported returns true iff hardware accelerated AES // is supported. is_supported :: proc "contextless" () -> bool { features, ok := info.cpu_features.? diff --git a/core/crypto/_aes/hw_intel/ghash.odin b/core/crypto/_aes/hw_intel/ghash.odin index 9a5208523..d61e71b3a 100644 --- a/core/crypto/_aes/hw_intel/ghash.odin +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -25,7 +25,6 @@ package aes_hw_intel import "base:intrinsics" import "core:crypto/_aes" -import "core:simd" import "core:simd/x86" @(private = "file") @@ -58,14 +57,11 @@ GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE // 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, -} +_BYTESWAP_INDEX: x86.__m128i : { 0x08090a0b0c0d0e0f, 0x0001020304050607 } @(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) + return x86._mm_shuffle_epi8(x, _BYTESWAP_INDEX) } // From a 128-bit value kw, compute kx as the XOR of the two 64-bit @@ -244,8 +240,8 @@ ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check { } // Process 1 block at a time - src: []byte for l > 0 { + src: []byte = --- if l >= _aes.GHASH_BLOCK_SIZE { src = buf buf = buf[_aes.GHASH_BLOCK_SIZE:] diff --git a/core/crypto/_chacha20/chacha20.odin b/core/crypto/_chacha20/chacha20.odin new file mode 100644 index 000000000..a907209de --- /dev/null +++ b/core/crypto/_chacha20/chacha20.odin @@ -0,0 +1,123 @@ +package _chacha20 + +import "base:intrinsics" +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +// KEY_SIZE is the (X)ChaCha20 key size in bytes. +KEY_SIZE :: 32 +// IV_SIZE is the ChaCha20 IV size in bytes. +IV_SIZE :: 12 +// XIV_SIZE is the XChaCha20 IV size in bytes. +XIV_SIZE :: 24 + +// MAX_CTR_IETF is the maximum counter value for the IETF flavor ChaCha20. +MAX_CTR_IETF :: 0xffffffff +// BLOCK_SIZE is the (X)ChaCha20 block size in bytes. +BLOCK_SIZE :: 64 +// STATE_SIZE_U32 is the (X)ChaCha20 state size in u32s. +STATE_SIZE_U32 :: 16 +// Rounds is the (X)ChaCha20 round count. +ROUNDS :: 20 + +// SIGMA_0 is sigma[0:4]. +SIGMA_0: u32 : 0x61707865 +// SIGMA_1 is sigma[4:8]. +SIGMA_1: u32 : 0x3320646e +// SIGMA_2 is sigma[8:12]. +SIGMA_2: u32 : 0x79622d32 +// SIGMA_3 is sigma[12:16]. +SIGMA_3: u32 : 0x6b206574 + +// Context is a ChaCha20 or XChaCha20 instance. +Context :: struct { + _s: [STATE_SIZE_U32]u32, + _buffer: [BLOCK_SIZE]byte, + _off: int, + _is_ietf_flavor: bool, + _is_initialized: bool, +} + +// init inititializes a Context for ChaCha20 with the provided key and +// iv. +// +// WARNING: This ONLY handles ChaCha20. XChaCha20 sub-key and IV +// derivation is expected to be handled by the caller, so that the +// HChaCha call can be suitably accelerated. +init :: proc "contextless" (ctx: ^Context, key, iv: []byte, is_xchacha: bool) { + if len(key) != KEY_SIZE || len(iv) != IV_SIZE { + intrinsics.trap() + } + + k, n := key, iv + + ctx._s[0] = SIGMA_0 + ctx._s[1] = SIGMA_1 + ctx._s[2] = SIGMA_2 + ctx._s[3] = SIGMA_3 + ctx._s[4] = endian.unchecked_get_u32le(k[0:4]) + ctx._s[5] = endian.unchecked_get_u32le(k[4:8]) + ctx._s[6] = endian.unchecked_get_u32le(k[8:12]) + ctx._s[7] = endian.unchecked_get_u32le(k[12:16]) + ctx._s[8] = endian.unchecked_get_u32le(k[16:20]) + ctx._s[9] = endian.unchecked_get_u32le(k[20:24]) + ctx._s[10] = endian.unchecked_get_u32le(k[24:28]) + ctx._s[11] = endian.unchecked_get_u32le(k[28:32]) + ctx._s[12] = 0 + ctx._s[13] = endian.unchecked_get_u32le(n[0:4]) + ctx._s[14] = endian.unchecked_get_u32le(n[4:8]) + ctx._s[15] = endian.unchecked_get_u32le(n[8:12]) + + ctx._off = BLOCK_SIZE + ctx._is_ietf_flavor = !is_xchacha + ctx._is_initialized = true +} + +// seek seeks the (X)ChaCha20 stream counter to the specified block. +seek :: proc(ctx: ^Context, block_nr: u64) { + assert(ctx._is_initialized) + + if ctx._is_ietf_flavor { + if block_nr > MAX_CTR_IETF { + panic("crypto/chacha20: attempted to seek past maximum counter") + } + } else { + ctx._s[13] = u32(block_nr >> 32) + } + ctx._s[12] = u32(block_nr) + ctx._off = BLOCK_SIZE +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + mem.zero_explicit(&ctx._s, size_of(ctx._s)) + mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) + + ctx._is_initialized = false +} + +check_counter_limit :: proc(ctx: ^Context, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + // + // While all modern "standard" definitions of ChaCha20 use + // the IETF 32-bit counter, for XChaCha20 most common + // implementations allow for a 64-bit counter. + // + // Honestly, the answer here is "use a MRAE primitive", but + // go with "common" practice in the case of XChaCha20. + + ERR_CTR_EXHAUSTED :: "crypto/chacha20: maximum (X)ChaCha20 keystream per IV reached" + + if ctx._is_ietf_flavor { + if u64(ctx._s[12]) + u64(nr_blocks) > MAX_CTR_IETF { + panic(ERR_CTR_EXHAUSTED) + } + } else { + ctr := (u64(ctx._s[13]) << 32) | u64(ctx._s[12]) + if _, carry := bits.add_u64(ctr, u64(nr_blocks), 0); carry != 0 { + panic(ERR_CTR_EXHAUSTED) + } + } +} diff --git a/core/crypto/_chacha20/ref/chacha20_ref.odin b/core/crypto/_chacha20/ref/chacha20_ref.odin new file mode 100644 index 000000000..c111c1c76 --- /dev/null +++ b/core/crypto/_chacha20/ref/chacha20_ref.odin @@ -0,0 +1,360 @@ +package chacha20_ref + +import "core:crypto/_chacha20" +import "core:encoding/endian" +import "core:math/bits" + +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + _chacha20.check_counter_limit(ctx, nr_blocks) + + dst, src := dst, src + x := &ctx._s + for n := 0; n < nr_blocks; n = n + 1 { + x0, x1, x2, x3 := + _chacha20.SIGMA_0, _chacha20.SIGMA_1, _chacha20.SIGMA_2, _chacha20.SIGMA_3 + x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := + x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15] + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + // Even when forcing inlining manually inlining all of + // these is decently faster. + + // quarterround(x, 0, 4, 8, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 16) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 8) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 7) + + // quarterround(x, 1, 5, 9, 13) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 16) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 12) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 8) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 2, 6, 10, 14) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 16) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 12) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 8) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 3, 7, 11, 15) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 16) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 12) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 8) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 0, 5, 10, 15) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 16) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 12) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 8) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 1, 6, 11, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 16) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 8) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 2, 7, 8, 13) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 16) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 12) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 8) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 3, 4, 9, 14) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 16) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 12) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 8) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 7) + } + + x0 += _chacha20.SIGMA_0 + x1 += _chacha20.SIGMA_1 + x2 += _chacha20.SIGMA_2 + x3 += _chacha20.SIGMA_3 + x4 += x[4] + x5 += x[5] + x6 += x[6] + x7 += x[7] + x8 += x[8] + x9 += x[9] + x10 += x[10] + x11 += x[11] + x12 += x[12] + x13 += x[13] + x14 += x[14] + x15 += x[15] + + // - The caller(s) ensure that src/dst are valid. + // - The compiler knows if the target is picky about alignment. + + #no_bounds_check { + if src != nil { + endian.unchecked_put_u32le(dst[0:4], endian.unchecked_get_u32le(src[0:4]) ~ x0) + endian.unchecked_put_u32le(dst[4:8], endian.unchecked_get_u32le(src[4:8]) ~ x1) + endian.unchecked_put_u32le(dst[8:12], endian.unchecked_get_u32le(src[8:12]) ~ x2) + endian.unchecked_put_u32le(dst[12:16], endian.unchecked_get_u32le(src[12:16]) ~ x3) + endian.unchecked_put_u32le(dst[16:20], endian.unchecked_get_u32le(src[16:20]) ~ x4) + endian.unchecked_put_u32le(dst[20:24], endian.unchecked_get_u32le(src[20:24]) ~ x5) + endian.unchecked_put_u32le(dst[24:28], endian.unchecked_get_u32le(src[24:28]) ~ x6) + endian.unchecked_put_u32le(dst[28:32], endian.unchecked_get_u32le(src[28:32]) ~ x7) + endian.unchecked_put_u32le(dst[32:36], endian.unchecked_get_u32le(src[32:36]) ~ x8) + endian.unchecked_put_u32le(dst[36:40], endian.unchecked_get_u32le(src[36:40]) ~ x9) + endian.unchecked_put_u32le( + dst[40:44], + endian.unchecked_get_u32le(src[40:44]) ~ x10, + ) + endian.unchecked_put_u32le( + dst[44:48], + endian.unchecked_get_u32le(src[44:48]) ~ x11, + ) + endian.unchecked_put_u32le( + dst[48:52], + endian.unchecked_get_u32le(src[48:52]) ~ x12, + ) + endian.unchecked_put_u32le( + dst[52:56], + endian.unchecked_get_u32le(src[52:56]) ~ x13, + ) + endian.unchecked_put_u32le( + dst[56:60], + endian.unchecked_get_u32le(src[56:60]) ~ x14, + ) + endian.unchecked_put_u32le( + dst[60:64], + endian.unchecked_get_u32le(src[60:64]) ~ x15, + ) + src = src[_chacha20.BLOCK_SIZE:] + } else { + endian.unchecked_put_u32le(dst[0:4], x0) + endian.unchecked_put_u32le(dst[4:8], x1) + endian.unchecked_put_u32le(dst[8:12], x2) + endian.unchecked_put_u32le(dst[12:16], x3) + endian.unchecked_put_u32le(dst[16:20], x4) + endian.unchecked_put_u32le(dst[20:24], x5) + endian.unchecked_put_u32le(dst[24:28], x6) + endian.unchecked_put_u32le(dst[28:32], x7) + endian.unchecked_put_u32le(dst[32:36], x8) + endian.unchecked_put_u32le(dst[36:40], x9) + endian.unchecked_put_u32le(dst[40:44], x10) + endian.unchecked_put_u32le(dst[44:48], x11) + endian.unchecked_put_u32le(dst[48:52], x12) + endian.unchecked_put_u32le(dst[52:56], x13) + endian.unchecked_put_u32le(dst[56:60], x14) + endian.unchecked_put_u32le(dst[60:64], x15) + } + dst = dst[_chacha20.BLOCK_SIZE:] + } + + // Increment the counter. Overflow checking is done upon + // entry into the routine, so a 64-bit increment safely + // covers both cases. + new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + 1 + x[12] = u32(new_ctr) + x[13] = u32(new_ctr >> 32) + } +} + +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + x0, x1, x2, x3 := _chacha20.SIGMA_0, _chacha20.SIGMA_1, _chacha20.SIGMA_2, _chacha20.SIGMA_3 + x4 := endian.unchecked_get_u32le(key[0:4]) + x5 := endian.unchecked_get_u32le(key[4:8]) + x6 := endian.unchecked_get_u32le(key[8:12]) + x7 := endian.unchecked_get_u32le(key[12:16]) + x8 := endian.unchecked_get_u32le(key[16:20]) + x9 := endian.unchecked_get_u32le(key[20:24]) + x10 := endian.unchecked_get_u32le(key[24:28]) + x11 := endian.unchecked_get_u32le(key[28:32]) + x12 := endian.unchecked_get_u32le(iv[0:4]) + x13 := endian.unchecked_get_u32le(iv[4:8]) + x14 := endian.unchecked_get_u32le(iv[8:12]) + x15 := endian.unchecked_get_u32le(iv[12:16]) + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + // quarterround(x, 0, 4, 8, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 16) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 8) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 7) + + // quarterround(x, 1, 5, 9, 13) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 16) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 12) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 8) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 2, 6, 10, 14) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 16) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 12) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 8) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 3, 7, 11, 15) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 16) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 12) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 8) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 0, 5, 10, 15) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 16) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 12) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 8) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 1, 6, 11, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 16) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 8) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 2, 7, 8, 13) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 16) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 12) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 8) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 3, 4, 9, 14) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 16) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 12) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 8) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 7) + } + + endian.unchecked_put_u32le(dst[0:4], x0) + endian.unchecked_put_u32le(dst[4:8], x1) + endian.unchecked_put_u32le(dst[8:12], x2) + endian.unchecked_put_u32le(dst[12:16], x3) + endian.unchecked_put_u32le(dst[16:20], x12) + endian.unchecked_put_u32le(dst[20:24], x13) + endian.unchecked_put_u32le(dst[24:28], x14) + endian.unchecked_put_u32le(dst[28:32], x15) +} diff --git a/core/crypto/_chacha20/simd128/chacha20_simd128.odin b/core/crypto/_chacha20/simd128/chacha20_simd128.odin new file mode 100644 index 000000000..2f91ac52a --- /dev/null +++ b/core/crypto/_chacha20/simd128/chacha20_simd128.odin @@ -0,0 +1,481 @@ +package chacha20_simd128 + +import "base:intrinsics" +import "core:crypto/_chacha20" +import "core:simd" +@(require) import "core:sys/info" + +// Portable 128-bit `core:simd` implementation. +// +// This is loosely based on Ted Krovetz's public domain C intrinsic +// implementation. +// +// This is written to perform adequately on any target that has "enough" +// 128-bit vector registers, the current thought is that 4 blocks at at +// time is reasonable for amd64, though Ted's code is more conservative. +// +// See: +// supercop-20230530/crypto_stream/chacha20/krovetz/vec128 + +// Ensure the compiler emits SIMD instructions. This is a minimum, and +// setting the microarchitecture at compile time will allow for better +// code gen when applicable (eg: AVX). This is somewhat redundant with +// the default microarchitecture configurations. +when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 { + @(private = "file") + TARGET_SIMD_FEATURES :: "neon" +} else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + // Note: LLVM appears to be smart enough to use PSHUFB despite not + // explicitly using simd.u8x16 shuffles. + @(private = "file") + TARGET_SIMD_FEATURES :: "sse2,ssse3" +} else { + @(private = "file") + TARGET_SIMD_FEATURES :: "" +} + +@(private = "file") +_ROT_7L: simd.u32x4 : {7, 7, 7, 7} +@(private = "file") +_ROT_7R: simd.u32x4 : {25, 25, 25, 25} +@(private = "file") +_ROT_12L: simd.u32x4 : {12, 12, 12, 12} +@(private = "file") +_ROT_12R: simd.u32x4 : {20, 20, 20, 20} +@(private = "file") +_ROT_8L: simd.u32x4 : {8, 8, 8, 8} +@(private = "file") +_ROT_8R: simd.u32x4 : {24, 24, 24, 24} +@(private = "file") +_ROT_16: simd.u32x4 : {16, 16, 16, 16} + +when ODIN_ENDIAN == .Big { + @(private = "file") + _increment_counter :: #force_inline proc "contextless" (ctx: ^Context) -> simd.u32x4 { + // In the Big Endian case, the low and high portions in the vector + // are flipped, so the 64-bit addition can't be done with a simple + // vector add. + x := &ctx._s + + new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + 1 + x[12] = u32(new_ctr) + x[13] = u32(new_ctr >> 32) + + return intrinsics.unaligned_load(transmute(^simd.u32x4)&x[12]) + } + + // Convert the endian-ness of the components of a u32x4 vector, for + // the purposes of output. + @(private = "file") + _byteswap_u32x4 :: #force_inline proc "contextless" (v: simd.u32x4) -> simd.u32x4 { + return( + transmute(simd.u32x4)simd.shuffle( + transmute(simd.u8x16)v, + transmute(simd.u8x16)v, + 3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12, + ) + ) + } +} else { + @(private = "file") + _VEC_ONE: simd.u64x2 : {1, 0} +} + +@(private = "file") +_dq_round_simd128 :: #force_inline proc "contextless" ( + v0, v1, v2, v3: simd.u32x4, +) -> ( + simd.u32x4, + simd.u32x4, + simd.u32x4, + simd.u32x4, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV1(b); c = ROTV2(c); d = ROTV3(d); + v1 = simd.shuffle(v1, v1, 1, 2, 3, 0) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1) + v3 = simd.shuffle(v3, v3, 3, 0, 1, 2) + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV3(b); c = ROTV2(c); d = ROTV1(d); + v1 = simd.shuffle(v1, v1, 3, 0, 1, 2) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1) + v3 = simd.shuffle(v3, v3, 1, 2, 3, 0) + + return v0, v1, v2, v3 +} + +@(private = "file") +_add_state_simd128 :: #force_inline proc "contextless" ( + v0, v1, v2, v3, s0, s1, s2, s3: simd.u32x4, +) -> ( + simd.u32x4, + simd.u32x4, + simd.u32x4, + simd.u32x4, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + v0 = simd.add(v0, s0) + v1 = simd.add(v1, s1) + v2 = simd.add(v2, s2) + v3 = simd.add(v3, s3) + + when ODIN_ENDIAN == .Big { + v0 = _byteswap_u32x4(v0) + v1 = _byteswap_u32x4(v1) + v2 = _byteswap_u32x4(v2) + v3 = _byteswap_u32x4(v3) + } + + return v0, v1, v2, v3 +} + +@(private = "file") +_xor_simd128 :: #force_inline proc "contextless" ( + src: [^]simd.u32x4, + v0, v1, v2, v3: simd.u32x4, +) -> ( + simd.u32x4, + simd.u32x4, + simd.u32x4, + simd.u32x4, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + v0 = simd.bit_xor(v0, intrinsics.unaligned_load((^simd.u32x4)(src[0:]))) + v1 = simd.bit_xor(v1, intrinsics.unaligned_load((^simd.u32x4)(src[1:]))) + v2 = simd.bit_xor(v2, intrinsics.unaligned_load((^simd.u32x4)(src[2:]))) + v3 = simd.bit_xor(v3, intrinsics.unaligned_load((^simd.u32x4)(src[3:]))) + + return v0, v1, v2, v3 +} + +@(private = "file") +_store_simd128 :: #force_inline proc "contextless" ( + dst: [^]simd.u32x4, + v0, v1, v2, v3: simd.u32x4, +) { + intrinsics.unaligned_store((^simd.u32x4)(dst[0:]), v0) + intrinsics.unaligned_store((^simd.u32x4)(dst[1:]), v1) + intrinsics.unaligned_store((^simd.u32x4)(dst[2:]), v2) + intrinsics.unaligned_store((^simd.u32x4)(dst[3:]), v3) +} + +// is_performant returns true iff the target and current host both support +// "enough" 128-bit SIMD to make this implementation performant. +is_performant :: proc "contextless" () -> bool { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 || ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 { + req_features :: info.CPU_Features{.asimd} + } else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + req_features :: info.CPU_Features{.sse2, .ssse3} + } + + features, ok := info.cpu_features.? + if !ok { + return false + } + + return features >= req_features + } else when ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32 { + return intrinsics.has_target_feature("simd128") + } else { + return false + } +} + +@(enable_target_feature = TARGET_SIMD_FEATURES) +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + _chacha20.check_counter_limit(ctx, nr_blocks) + + dst_v := ([^]simd.u32x4)(raw_data(dst)) + src_v := ([^]simd.u32x4)(raw_data(src)) + + x := &ctx._s + n := nr_blocks + + // The state vector is an array of uint32s in native byte-order. + x_v := ([^]simd.u32x4)(raw_data(x)) + s0 := intrinsics.unaligned_load((^simd.u32x4)(x_v[0:])) + s1 := intrinsics.unaligned_load((^simd.u32x4)(x_v[1:])) + s2 := intrinsics.unaligned_load((^simd.u32x4)(x_v[2:])) + s3 := intrinsics.unaligned_load((^simd.u32x4)(x_v[3:])) + + // 8 blocks at a time. + // + // Note: This is only worth it on Aarch64. + when ODIN_ARCH == .arm64 { + for ; n >= 8; n = n - 8 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + when ODIN_ENDIAN == .Little { + s7 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s3, _VEC_ONE) + } else { + s7 := _increment_counter(ctx) + } + v4, v5, v6, v7 := s0, s1, s2, s7 + + when ODIN_ENDIAN == .Little { + s11 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s7, _VEC_ONE) + } else { + s11 := _increment_counter(ctx) + } + v8, v9, v10, v11 := s0, s1, s2, s11 + + when ODIN_ENDIAN == .Little { + s15 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s11, _VEC_ONE) + } else { + s15 := _increment_counter(ctx) + } + v12, v13, v14, v15 := s0, s1, s2, s15 + + when ODIN_ENDIAN == .Little { + s19 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s15, _VEC_ONE) + } else { + s19 := _increment_counter(ctx) + } + + v16, v17, v18, v19 := s0, s1, s2, s19 + when ODIN_ENDIAN == .Little { + s23 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s19, _VEC_ONE) + } else { + s23 := _increment_counter(ctx) + } + + v20, v21, v22, v23 := s0, s1, s2, s23 + when ODIN_ENDIAN == .Little { + s27 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s23, _VEC_ONE) + } else { + s27 := _increment_counter(ctx) + } + + v24, v25, v26, v27 := s0, s1, s2, s27 + when ODIN_ENDIAN == .Little { + s31 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s27, _VEC_ONE) + } else { + s31 := _increment_counter(ctx) + } + v28, v29, v30, v31 := s0, s1, s2, s31 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + v4, v5, v6, v7 = _dq_round_simd128(v4, v5, v6, v7) + v8, v9, v10, v11 = _dq_round_simd128(v8, v9, v10, v11) + v12, v13, v14, v15 = _dq_round_simd128(v12, v13, v14, v15) + v16, v17, v18, v19 = _dq_round_simd128(v16, v17, v18, v19) + v20, v21, v22, v23 = _dq_round_simd128(v20, v21, v22, v23) + v24, v25, v26, v27 = _dq_round_simd128(v24, v25, v26, v27) + v28, v29, v30, v31 = _dq_round_simd128(v28, v29, v30, v31) + } + + v0, v1, v2, v3 = _add_state_simd128(v0, v1, v2, v3, s0, s1, s2, s3) + v4, v5, v6, v7 = _add_state_simd128(v4, v5, v6, v7, s0, s1, s2, s7) + v8, v9, v10, v11 = _add_state_simd128(v8, v9, v10, v11, s0, s1, s2, s11) + v12, v13, v14, v15 = _add_state_simd128(v12, v13, v14, v15, s0, s1, s2, s15) + v16, v17, v18, v19 = _add_state_simd128(v16, v17, v18, v19, s0, s1, s2, s19) + v20, v21, v22, v23 = _add_state_simd128(v20, v21, v22, v23, s0, s1, s2, s23) + v24, v25, v26, v27 = _add_state_simd128(v24, v25, v26, v27, s0, s1, s2, s27) + v28, v29, v30, v31 = _add_state_simd128(v28, v29, v30, v31, s0, s1, s2, s31) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd128(src_v, v0, v1, v2, v3) + v4, v5, v6, v7 = _xor_simd128(src_v[4:], v4, v5, v6, v7) + v8, v9, v10, v11 = _xor_simd128(src_v[8:], v8, v9, v10, v11) + v12, v13, v14, v15 = _xor_simd128(src_v[12:], v12, v13, v14, v15) + v16, v17, v18, v19 = _xor_simd128(src_v[16:], v16, v17, v18, v19) + v20, v21, v22, v23 = _xor_simd128(src_v[20:], v20, v21, v22, v23) + v24, v25, v26, v27 = _xor_simd128(src_v[24:], v24, v25, v26, v27) + v28, v29, v30, v31 = _xor_simd128(src_v[28:], v28, v29, v30, v31) + src_v = src_v[32:] + } + + _store_simd128(dst_v, v0, v1, v2, v3) + _store_simd128(dst_v[4:], v4, v5, v6, v7) + _store_simd128(dst_v[8:], v8, v9, v10, v11) + _store_simd128(dst_v[12:], v12, v13, v14, v15) + _store_simd128(dst_v[16:], v16, v17, v18, v19) + _store_simd128(dst_v[20:], v20, v21, v22, v23) + _store_simd128(dst_v[24:], v24, v25, v26, v27) + _store_simd128(dst_v[28:], v28, v29, v30, v31) + dst_v = dst_v[32:] + } + + when ODIN_ENDIAN == .Little { + // s31 holds the most current counter, so `s3 = s31 + 1`. + s3 = transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s31, _VEC_ONE) + } else { + s3 = _increment_counter(ctx) + } + } + } + + // 4 blocks at a time. + // + // Note: The i386 target lacks the required number of registers + // for this to be performant, so it is skipped. + when ODIN_ARCH != .i386 { + for ; n >= 4; n = n - 4 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + when ODIN_ENDIAN == .Little { + s7 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s3, _VEC_ONE) + } else { + s7 := _increment_counter(ctx) + } + v4, v5, v6, v7 := s0, s1, s2, s7 + + when ODIN_ENDIAN == .Little { + s11 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s7, _VEC_ONE) + } else { + s11 := _increment_counter(ctx) + } + v8, v9, v10, v11 := s0, s1, s2, s11 + + when ODIN_ENDIAN == .Little { + s15 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s11, _VEC_ONE) + } else { + s15 := _increment_counter(ctx) + } + v12, v13, v14, v15 := s0, s1, s2, s15 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + v4, v5, v6, v7 = _dq_round_simd128(v4, v5, v6, v7) + v8, v9, v10, v11 = _dq_round_simd128(v8, v9, v10, v11) + v12, v13, v14, v15 = _dq_round_simd128(v12, v13, v14, v15) + } + + v0, v1, v2, v3 = _add_state_simd128(v0, v1, v2, v3, s0, s1, s2, s3) + v4, v5, v6, v7 = _add_state_simd128(v4, v5, v6, v7, s0, s1, s2, s7) + v8, v9, v10, v11 = _add_state_simd128(v8, v9, v10, v11, s0, s1, s2, s11) + v12, v13, v14, v15 = _add_state_simd128(v12, v13, v14, v15, s0, s1, s2, s15) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd128(src_v, v0, v1, v2, v3) + v4, v5, v6, v7 = _xor_simd128(src_v[4:], v4, v5, v6, v7) + v8, v9, v10, v11 = _xor_simd128(src_v[8:], v8, v9, v10, v11) + v12, v13, v14, v15 = _xor_simd128(src_v[12:], v12, v13, v14, v15) + src_v = src_v[16:] + } + + _store_simd128(dst_v, v0, v1, v2, v3) + _store_simd128(dst_v[4:], v4, v5, v6, v7) + _store_simd128(dst_v[8:], v8, v9, v10, v11) + _store_simd128(dst_v[12:], v12, v13, v14, v15) + dst_v = dst_v[16:] + } + + when ODIN_ENDIAN == .Little { + // s15 holds the most current counter, so `s3 = s15 + 1`. + s3 = transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s15, _VEC_ONE) + } else { + s3 = _increment_counter(ctx) + } + } + } + + // 1 block at a time. + for ; n > 0; n = n - 1 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + } + v0, v1, v2, v3 = _add_state_simd128(v0, v1, v2, v3, s0, s1, s2, s3) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd128(src_v, v0, v1, v2, v3) + src_v = src_v[4:] + } + + _store_simd128(dst_v, v0, v1, v2, v3) + dst_v = dst_v[4:] + } + + // Increment the counter. Overflow checking is done upon + // entry into the routine, so a 64-bit increment safely + // covers both cases. + when ODIN_ENDIAN == .Little { + s3 = transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s3, _VEC_ONE) + } else { + s3 = _increment_counter(ctx) + } + } + + when ODIN_ENDIAN == .Little { + // Write back the counter to the state. + intrinsics.unaligned_store((^simd.u32x4)(x_v[3:]), s3) + } +} + +@(enable_target_feature = TARGET_SIMD_FEATURES) +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + v0 := simd.u32x4{_chacha20.SIGMA_0, _chacha20.SIGMA_1, _chacha20.SIGMA_2, _chacha20.SIGMA_3} + v1 := intrinsics.unaligned_load((^simd.u32x4)(&key[0])) + v2 := intrinsics.unaligned_load((^simd.u32x4)(&key[16])) + v3 := intrinsics.unaligned_load((^simd.u32x4)(&iv[0])) + + when ODIN_ENDIAN == .Big { + v1 = _byteswap_u32x4(v1) + v2 = _byteswap_u32x4(v2) + v3 = _byteswap_u32x4(v3) + } + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + } + + when ODIN_ENDIAN == .Big { + v0 = _byteswap_u32x4(v0) + v3 = _byteswap_u32x4(v3) + } + + dst_v := ([^]simd.u32x4)(raw_data(dst)) + intrinsics.unaligned_store((^simd.u32x4)(dst_v[0:]), v0) + intrinsics.unaligned_store((^simd.u32x4)(dst_v[1:]), v3) +} diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256.odin b/core/crypto/_chacha20/simd256/chacha20_simd256.odin new file mode 100644 index 000000000..10f2d75fe --- /dev/null +++ b/core/crypto/_chacha20/simd256/chacha20_simd256.odin @@ -0,0 +1,319 @@ +//+build amd64 +package chacha20_simd256 + +import "base:intrinsics" +import "core:crypto/_chacha20" +import chacha_simd128 "core:crypto/_chacha20/simd128" +import "core:simd" +import "core:sys/info" + +// This is loosely based on Ted Krovetz's public domain C intrinsic +// implementations. While written using `core:simd`, this is currently +// amd64 specific because we do not have a way to detect ARM SVE. +// +// See: +// supercop-20230530/crypto_stream/chacha20/krovetz/vec128 +// supercop-20230530/crypto_stream/chacha20/krovetz/avx2 + +#assert(ODIN_ENDIAN == .Little) + +@(private = "file") +_ROT_7L: simd.u32x8 : {7, 7, 7, 7, 7, 7, 7, 7} +@(private = "file") +_ROT_7R: simd.u32x8 : {25, 25, 25, 25, 25, 25, 25, 25} +@(private = "file") +_ROT_12L: simd.u32x8 : {12, 12, 12, 12, 12, 12, 12, 12} +@(private = "file") +_ROT_12R: simd.u32x8 : {20, 20, 20, 20, 20, 20, 20, 20} +@(private = "file") +_ROT_8L: simd.u32x8 : {8, 8, 8, 8, 8, 8, 8, 8} +@(private = "file") +_ROT_8R: simd.u32x8 : {24, 24, 24, 24, 24, 24, 24, 24} +@(private = "file") +_ROT_16: simd.u32x8 : {16, 16, 16, 16, 16, 16, 16, 16} +@(private = "file") +_VEC_ZERO_ONE: simd.u64x4 : {0, 0, 1, 0} +@(private = "file") +_VEC_TWO: simd.u64x4 : {2, 0, 2, 0} + +// is_performant returns true iff the target and current host both support +// "enough" SIMD to make this implementation performant. +is_performant :: proc "contextless" () -> bool { + req_features :: info.CPU_Features{.avx, .avx2} + + features, ok := info.cpu_features.? + if !ok { + return false + } + + return features >= req_features +} + +@(private = "file") +_dq_round_simd256 :: #force_inline proc "contextless" ( + v0, v1, v2, v3: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, + simd.u32x8, + simd.u32x8, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV1(b); c = ROTV2(c); d = ROTV3(d); + v1 = simd.shuffle(v1, v1, 1, 2, 3, 0, 5, 6, 7, 4) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1, 6, 7, 4, 5) + v3 = simd.shuffle(v3, v3, 3, 0, 1, 2, 7, 4, 5, 6) + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV3(b); c = ROTV2(c); d = ROTV1(d); + v1 = simd.shuffle(v1, v1, 3, 0, 1, 2, 7, 4, 5, 6) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1, 6, 7, 4, 5) + v3 = simd.shuffle(v3, v3, 1, 2, 3, 0, 5, 6, 7, 4) + + return v0, v1, v2, v3 +} + +@(private = "file") +_add_and_permute_state_simd256 :: #force_inline proc "contextless" ( + v0, v1, v2, v3, s0, s1, s2, s3: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, + simd.u32x8, + simd.u32x8, +) { + t0 := simd.add(v0, s0) + t1 := simd.add(v1, s1) + t2 := simd.add(v2, s2) + t3 := simd.add(v3, s3) + + // Big Endian would byteswap here. + + // Each of v0 .. v3 has 128-bits of keystream for 2 separate blocks. + // permute the state such that (r0, r1) contains block 0, and (r2, r3) + // contains block 1. + r0 := simd.shuffle(t0, t1, 0, 1, 2, 3, 8, 9, 10, 11) + r2 := simd.shuffle(t0, t1, 4, 5, 6, 7, 12, 13, 14, 15) + r1 := simd.shuffle(t2, t3, 0, 1, 2, 3, 8, 9, 10, 11) + r3 := simd.shuffle(t2, t3, 4, 5, 6, 7, 12, 13, 14, 15) + + return r0, r1, r2, r3 +} + +@(private = "file") +_xor_simd256 :: #force_inline proc "contextless" ( + src: [^]simd.u32x8, + v0, v1, v2, v3: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, + simd.u32x8, + simd.u32x8, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + v0 = simd.bit_xor(v0, intrinsics.unaligned_load((^simd.u32x8)(src[0:]))) + v1 = simd.bit_xor(v1, intrinsics.unaligned_load((^simd.u32x8)(src[1:]))) + v2 = simd.bit_xor(v2, intrinsics.unaligned_load((^simd.u32x8)(src[2:]))) + v3 = simd.bit_xor(v3, intrinsics.unaligned_load((^simd.u32x8)(src[3:]))) + + return v0, v1, v2, v3 +} + +@(private = "file") +_xor_simd256_x1 :: #force_inline proc "contextless" ( + src: [^]simd.u32x8, + v0, v1: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, +) { + v0, v1 := v0, v1 + + v0 = simd.bit_xor(v0, intrinsics.unaligned_load((^simd.u32x8)(src[0:]))) + v1 = simd.bit_xor(v1, intrinsics.unaligned_load((^simd.u32x8)(src[1:]))) + + return v0, v1 +} + +@(private = "file") +_store_simd256 :: #force_inline proc "contextless" ( + dst: [^]simd.u32x8, + v0, v1, v2, v3: simd.u32x8, +) { + intrinsics.unaligned_store((^simd.u32x8)(dst[0:]), v0) + intrinsics.unaligned_store((^simd.u32x8)(dst[1:]), v1) + intrinsics.unaligned_store((^simd.u32x8)(dst[2:]), v2) + intrinsics.unaligned_store((^simd.u32x8)(dst[3:]), v3) +} + +@(private = "file") +_store_simd256_x1 :: #force_inline proc "contextless" ( + dst: [^]simd.u32x8, + v0, v1: simd.u32x8, +) { + intrinsics.unaligned_store((^simd.u32x8)(dst[0:]), v0) + intrinsics.unaligned_store((^simd.u32x8)(dst[1:]), v1) +} + +@(enable_target_feature = "sse2,ssse3,avx,avx2") +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + _chacha20.check_counter_limit(ctx, nr_blocks) + + dst_v := ([^]simd.u32x8)(raw_data(dst)) + src_v := ([^]simd.u32x8)(raw_data(src)) + + x := &ctx._s + n := nr_blocks + + // The state vector is an array of uint32s in native byte-order. + // Setup s0 .. s3 such that each register stores 2 copies of the + // state. + x_v := ([^]simd.u32x4)(raw_data(x)) + t0 := intrinsics.unaligned_load((^simd.u32x4)(x_v[0:])) + t1 := intrinsics.unaligned_load((^simd.u32x4)(x_v[1:])) + t2 := intrinsics.unaligned_load((^simd.u32x4)(x_v[2:])) + t3 := intrinsics.unaligned_load((^simd.u32x4)(x_v[3:])) + s0 := simd.swizzle(t0, 0, 1, 2, 3, 0, 1, 2, 3) + s1 := simd.swizzle(t1, 0, 1, 2, 3, 0, 1, 2, 3) + s2 := simd.swizzle(t2, 0, 1, 2, 3, 0, 1, 2, 3) + s3 := simd.swizzle(t3, 0, 1, 2, 3, 0, 1, 2, 3) + + // Advance the counter in the 2nd copy of the state by one. + s3 = transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s3, _VEC_ZERO_ONE) + + // 8 blocks at a time. + for ; n >= 8; n = n - 8 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + s7 := transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s3, _VEC_TWO) + v4, v5, v6, v7 := s0, s1, s2, s7 + + s11 := transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s7, _VEC_TWO) + v8, v9, v10, v11 := s0, s1, s2, s11 + + s15 := transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s11, _VEC_TWO) + v12, v13, v14, v15 := s0, s1, s2, s15 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd256(v0, v1, v2, v3) + v4, v5, v6, v7 = _dq_round_simd256(v4, v5, v6, v7) + v8, v9, v10, v11 = _dq_round_simd256(v8, v9, v10, v11) + v12, v13, v14, v15 = _dq_round_simd256(v12, v13, v14, v15) + } + + v0, v1, v2, v3 = _add_and_permute_state_simd256(v0, v1, v2, v3, s0, s1, s2, s3) + v4, v5, v6, v7 = _add_and_permute_state_simd256(v4, v5, v6, v7, s0, s1, s2, s7) + v8, v9, v10, v11 = _add_and_permute_state_simd256(v8, v9, v10, v11, s0, s1, s2, s11) + v12, v13, v14, v15 = _add_and_permute_state_simd256(v12, v13, v14, v15, s0, s1, s2, s15) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd256(src_v, v0, v1, v2, v3) + v4, v5, v6, v7 = _xor_simd256(src_v[4:], v4, v5, v6, v7) + v8, v9, v10, v11 = _xor_simd256(src_v[8:], v8, v9, v10, v11) + v12, v13, v14, v15 = _xor_simd256(src_v[12:], v12, v13, v14, v15) + src_v = src_v[16:] + } + + _store_simd256(dst_v, v0, v1, v2, v3) + _store_simd256(dst_v[4:], v4, v5, v6, v7) + _store_simd256(dst_v[8:], v8, v9, v10, v11) + _store_simd256(dst_v[12:], v12, v13, v14, v15) + dst_v = dst_v[16:] + } + + s3 = transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s15, _VEC_TWO) + } + + + // 2 (or 1) block at a time. + for ; n > 0; n = n - 2 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd256(v0, v1, v2, v3) + } + v0, v1, v2, v3 = _add_and_permute_state_simd256(v0, v1, v2, v3, s0, s1, s2, s3) + + if n == 1 { + // Note: No need to advance src_v, dst_v, or increment the counter + // since this is guaranteed to be the final block. + #no_bounds_check { + if src != nil { + v0, v1 = _xor_simd256_x1(src_v, v0, v1) + } + + _store_simd256_x1(dst_v, v0, v1) + } + break + } + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd256(src_v, v0, v1, v2, v3) + src_v = src_v[4:] + } + + _store_simd256(dst_v, v0, v1, v2, v3) + dst_v = dst_v[4:] + } + + s3 = transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s3, _VEC_TWO) + } + + // Write back the counter. Doing it this way, saves having to + // pull out the correct counter value from s3. + new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + u64(nr_blocks) + ctx._s[12] = u32(new_ctr) + ctx._s[13] = u32(new_ctr >> 32) +} + +@(enable_target_feature = "sse2,ssse3,avx") +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + // We can just enable AVX and call the simd128 code as going + // wider has 0 performance benefit, but VEX encoded instructions + // is nice. + #force_inline chacha_simd128.hchacha20(dst, key, iv) +} \ No newline at end of file diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin new file mode 100644 index 000000000..039d6cb96 --- /dev/null +++ b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin @@ -0,0 +1,17 @@ +//+build !amd64 +package chacha20_simd256 + +import "base:intrinsics" +import "core:crypto/_chacha20" + +is_performant :: proc "contextless" () -> bool { + return false +} + +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + panic("crypto/chacha20: simd256 implementation unsupported") +} + +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + intrinsics.trap() +} \ No newline at end of file diff --git a/core/crypto/aead/aead.odin b/core/crypto/aead/aead.odin new file mode 100644 index 000000000..9b7d810e4 --- /dev/null +++ b/core/crypto/aead/aead.odin @@ -0,0 +1,36 @@ +package aead + +// seal_oneshot encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided algorithm, key, and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal_oneshot :: proc(algo: Algorithm, dst, tag, key, iv, aad, plaintext: []byte, impl: Implementation = nil) { + ctx: Context + init(&ctx, algo, key, impl) + defer reset(&ctx) + seal_ctx(&ctx, dst, tag, iv, aad, plaintext) +} + +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided algorithm, key, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open_oneshot :: proc(algo: Algorithm, dst, key, iv, aad, ciphertext, tag: []byte, impl: Implementation = nil) -> bool { + ctx: Context + init(&ctx, algo, key, impl) + defer reset(&ctx) + return open_ctx(&ctx, dst, iv, aad, ciphertext, tag) +} + +seal :: proc { + seal_ctx, + seal_oneshot, +} + +open :: proc { + open_ctx, + open_oneshot, +} diff --git a/core/crypto/aead/doc.odin b/core/crypto/aead/doc.odin new file mode 100644 index 000000000..93be674a0 --- /dev/null +++ b/core/crypto/aead/doc.odin @@ -0,0 +1,57 @@ +/* +package aead provides a generic interface to the supported Authenticated +Encryption with Associated Data algorithms. + +Both a one-shot and context based interface are provided, with similar +usage. If multiple messages are to be sealed/opened via the same key, +the context based interface may be more efficient, depending on the +algorithm. + +WARNING: Reusing the same key + iv to seal (encrypt) multiple messages +results in catastrophic loss of security for most algorithms. + +Example: + package aead_example + + import "core:bytes" + import "core:crypto" + import "core:crypto/aead" + + main :: proc() { + algo := aead.Algorithm.XCHACHA20POLY1305 + + // The example added associated data, and plaintext. + aad_str := "Get your ass in gear boys." + pt_str := "They're immanetizing the Eschaton." + + aad := transmute([]byte)aad_str + plaintext := transmute([]byte)pt_str + pt_len := len(plaintext) + + // Generate a random key for the purposes of illustration. + key := make([]byte, aead.KEY_SIZES[algo]) + defer delete(key) + crypto.rand_bytes(key) + + // `ciphertext || tag`, is a common way data is transmitted, so + // demonstrate that. + buf := make([]byte, pt_len + aead.TAG_SIZES[algo]) + defer delete(buf) + ciphertext, tag := buf[:pt_len], buf[pt_len:] + + // Seal the AAD + Plaintext. + iv := make([]byte, aead.IV_SIZES[algo]) + defer delete(iv) + crypto.rand_bytes(iv) // Random IVs are safe with XChaCha20-Poly1305. + aead.seal(algo, ciphertext, tag, key, iv, aad, plaintext) + + // Open the AAD + Ciphertext. + opened_pt := buf[:pt_len] + if ok := aead.open(algo, opened_pt, key, iv, aad, ciphertext, tag); !ok { + panic("aead example: failed to open") + } + + assert(bytes.equal(opened_pt, plaintext)) + } +*/ +package aead diff --git a/core/crypto/aead/low_level.odin b/core/crypto/aead/low_level.odin new file mode 100644 index 000000000..38a0c84ba --- /dev/null +++ b/core/crypto/aead/low_level.odin @@ -0,0 +1,187 @@ +package aead + +import "core:crypto/aes" +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" +import "core:reflect" + +// Implementation is an AEAD implementation. Most callers will not need +// to use this as the package will automatically select the most performant +// implementation available. +Implementation :: union { + aes.Implementation, + chacha20.Implementation, +} + +// MAX_TAG_SIZE is the maximum size tag that can be returned by any of the +// Algorithms supported via this package. +MAX_TAG_SIZE :: 16 + +// Algorithm is the algorithm identifier associated with a given Context. +Algorithm :: enum { + Invalid, + AES_GCM_128, + AES_GCM_192, + AES_GCM_256, + CHACHA20POLY1305, + XCHACHA20POLY1305, +} + +// ALGORITM_NAMES is the Agorithm to algorithm name string. +ALGORITHM_NAMES := [Algorithm]string { + .Invalid = "Invalid", + .AES_GCM_128 = "AES-GCM-128", + .AES_GCM_192 = "AES-GCM-192", + .AES_GCM_256 = "AES-GCM-256", + .CHACHA20POLY1305 = "chacha20poly1305", + .XCHACHA20POLY1305 = "xchacha20poly1305", +} + +// TAG_SIZES is the Algorithm to tag size in bytes. +TAG_SIZES := [Algorithm]int { + .Invalid = 0, + .AES_GCM_128 = aes.GCM_TAG_SIZE, + .AES_GCM_192 = aes.GCM_TAG_SIZE, + .AES_GCM_256 = aes.GCM_TAG_SIZE, + .CHACHA20POLY1305 = chacha20poly1305.TAG_SIZE, + .XCHACHA20POLY1305 = chacha20poly1305.TAG_SIZE, +} + +// KEY_SIZES is the Algorithm to key size in bytes. +KEY_SIZES := [Algorithm]int { + .Invalid = 0, + .AES_GCM_128 = aes.KEY_SIZE_128, + .AES_GCM_192 = aes.KEY_SIZE_192, + .AES_GCM_256 = aes.KEY_SIZE_256, + .CHACHA20POLY1305 = chacha20poly1305.KEY_SIZE, + .XCHACHA20POLY1305 = chacha20poly1305.KEY_SIZE, +} + +// IV_SIZES is the Algorithm to initialization vector size in bytes. +// +// Note: Some algorithms (such as AES-GCM) support variable IV sizes. +IV_SIZES := [Algorithm]int { + .Invalid = 0, + .AES_GCM_128 = aes.GCM_IV_SIZE, + .AES_GCM_192 = aes.GCM_IV_SIZE, + .AES_GCM_256 = aes.GCM_IV_SIZE, + .CHACHA20POLY1305 = chacha20poly1305.IV_SIZE, + .XCHACHA20POLY1305 = chacha20poly1305.XIV_SIZE, +} + +// Context is a concrete instantiation of a specific AEAD algorithm. +Context :: struct { + _algo: Algorithm, + _impl: union { + aes.Context_GCM, + chacha20poly1305.Context, + }, +} + +@(private) +_IMPL_IDS := [Algorithm]typeid { + .Invalid = nil, + .AES_GCM_128 = typeid_of(aes.Context_GCM), + .AES_GCM_192 = typeid_of(aes.Context_GCM), + .AES_GCM_256 = typeid_of(aes.Context_GCM), + .CHACHA20POLY1305 = typeid_of(chacha20poly1305.Context), + .XCHACHA20POLY1305 = typeid_of(chacha20poly1305.Context), +} + +// init initializes a Context with a specific AEAD Algorithm. +init :: proc(ctx: ^Context, algorithm: Algorithm, key: []byte, impl: Implementation = nil) { + if ctx._impl != nil { + reset(ctx) + } + + if len(key) != KEY_SIZES[algorithm] { + panic("crypto/aead: invalid key size") + } + + // Directly specialize the union by setting the type ID (save a copy). + reflect.set_union_variant_typeid( + ctx._impl, + _IMPL_IDS[algorithm], + ) + switch algorithm { + case .AES_GCM_128, .AES_GCM_192, .AES_GCM_256: + impl_ := impl != nil ? impl.(aes.Implementation) : aes.DEFAULT_IMPLEMENTATION + aes.init_gcm(&ctx._impl.(aes.Context_GCM), key, impl_) + case .CHACHA20POLY1305: + impl_ := impl != nil ? impl.(chacha20.Implementation) : chacha20.DEFAULT_IMPLEMENTATION + chacha20poly1305.init(&ctx._impl.(chacha20poly1305.Context), key, impl_) + case .XCHACHA20POLY1305: + impl_ := impl != nil ? impl.(chacha20.Implementation) : chacha20.DEFAULT_IMPLEMENTATION + chacha20poly1305.init_xchacha(&ctx._impl.(chacha20poly1305.Context), key, impl_) + case .Invalid: + panic("crypto/aead: uninitialized algorithm") + case: + panic("crypto/aead: invalid algorithm") + } + + ctx._algo = algorithm +} + +// seal_ctx encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal_ctx :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + switch &impl in ctx._impl { + case aes.Context_GCM: + aes.seal_gcm(&impl, dst, tag, iv, aad, plaintext) + case chacha20poly1305.Context: + chacha20poly1305.seal(&impl, dst, tag, iv, aad, plaintext) + case: + panic("crypto/aead: uninitialized algorithm") + } +} + +// open_ctx authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open_ctx :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + switch &impl in ctx._impl { + case aes.Context_GCM: + return aes.open_gcm(&impl, dst, iv, aad, ciphertext, tag) + case chacha20poly1305.Context: + return chacha20poly1305.open(&impl, dst, iv, aad, ciphertext, tag) + case: + panic("crypto/aead: uninitialized algorithm") + } +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + switch &impl in ctx._impl { + case aes.Context_GCM: + aes.reset_gcm(&impl) + case chacha20poly1305.Context: + chacha20poly1305.reset(&impl) + case: + // Calling reset repeatedly is fine. + } + + ctx._algo = .Invalid + ctx._impl = nil +} + +// algorithm returns the Algorithm used by a Context instance. +algorithm :: proc(ctx: ^Context) -> Algorithm { + return ctx._algo +} + +// iv_size returns the IV size of a Context instance in bytes. +iv_size :: proc(ctx: ^Context) -> int { + return IV_SIZES[ctx._algo] +} + +// tag_size returns the tag size of a Context instance in bytes. +tag_size :: proc(ctx: ^Context) -> int { + return TAG_SIZES[ctx._algo] +} diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin index ef305fd21..57f49acf4 100644 --- a/core/crypto/aes/aes.odin +++ b/core/crypto/aes/aes.odin @@ -2,9 +2,9 @@ package aes implements the AES block cipher and some common modes. See: -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf -- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf -- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf ]] +- [[ https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf ]] +- [[ https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf ]] */ package aes diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin index 1c5fe31e8..20b75e57f 100644 --- a/core/crypto/aes/aes_ctr.odin +++ b/core/crypto/aes/aes_ctr.odin @@ -20,7 +20,7 @@ Context_CTR :: struct { } // init_ctr initializes a Context_CTR with the provided key and IV. -init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hardware) { +init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := DEFAULT_IMPLEMENTATION) { if len(iv) != CTR_IV_SIZE { panic("crypto/aes: invalid CTR IV size") } @@ -47,7 +47,7 @@ xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { panic("crypto/aes: dst and src alias inexactly") } - for remaining := len(src); remaining > 0; { + #no_bounds_check for remaining := len(src); remaining > 0; { // Process multiple blocks at once if ctx._off == BLOCK_SIZE { if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { @@ -85,7 +85,7 @@ keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) { assert(ctx._is_initialized) dst := dst - for remaining := len(dst); remaining > 0; { + #no_bounds_check for remaining := len(dst); remaining > 0; { // Process multiple blocks at once if ctx._off == BLOCK_SIZE { if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { diff --git a/core/crypto/aes/aes_ecb.odin b/core/crypto/aes/aes_ecb.odin index 498429e29..32476006c 100644 --- a/core/crypto/aes/aes_ecb.odin +++ b/core/crypto/aes/aes_ecb.odin @@ -12,7 +12,7 @@ Context_ECB :: struct { } // init_ecb initializes a Context_ECB with the provided key. -init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := Implementation.Hardware) { +init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := DEFAULT_IMPLEMENTATION) { init_impl(&ctx._impl, key, impl) ctx._is_initialized = true } diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin index 25e0cc35b..8616821ce 100644 --- a/core/crypto/aes/aes_gcm.odin +++ b/core/crypto/aes/aes_gcm.odin @@ -7,10 +7,10 @@ import "core:crypto/_aes/ct64" import "core:encoding/endian" import "core:mem" -// 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_IV_SIZE is the default size of the GCM IV in bytes. +GCM_IV_SIZE :: 12 +// GCM_IV_SIZE_MAX is the maximum size of the GCM IV in bytes. +GCM_IV_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 @@ -26,19 +26,19 @@ Context_GCM :: struct { } // init_gcm initializes a Context_GCM with the provided key. -init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := Implementation.Hardware) { +init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := DEFAULT_IMPLEMENTATION) { init_impl(&ctx._impl, key, impl) ctx._is_initialized = true } // seal_gcm encrypts the plaintext and authenticates the aad and ciphertext, -// with the provided Context_GCM and nonce, stores the output in dst and tag. +// with the provided Context_GCM and iv, stores the output in dst and tag. // // dst and plaintext MUST alias exactly or not at all. -seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { +seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, iv, aad, plaintext: []byte) { assert(ctx._is_initialized) - gcm_validate_common_slice_sizes(tag, nonce, aad, plaintext) + gcm_validate_common_slice_sizes(tag, iv, aad, plaintext) if len(dst) != len(plaintext) { panic("crypto/aes: invalid destination ciphertext size") } @@ -47,7 +47,7 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { } if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { - gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext) + gcm_seal_hw(&impl, dst, tag, iv, aad, plaintext) return } @@ -55,7 +55,7 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []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, &j0_enc, nonce) + init_ghash_ct64(ctx, &h, &j0, &j0_enc, iv) // Note: Our GHASH implementation handles appending padding. ct64.ghash(s[:], h[:], aad) @@ -69,15 +69,16 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { } // open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext, -// with the provided Context_GCM, nonce, and tag, and stores the output in dst, +// with the provided Context_GCM, iv, and tag, and stores the output in dst, // returning true iff the authentication was successful. If authentication // fails, the destination buffer will be zeroed. // // dst and plaintext MUST alias exactly or not at all. -open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> bool { +@(require_results) +open_gcm :: proc(ctx: ^Context_GCM, dst, iv, aad, ciphertext, tag: []byte) -> bool { assert(ctx._is_initialized) - gcm_validate_common_slice_sizes(tag, nonce, aad, ciphertext) + gcm_validate_common_slice_sizes(tag, iv, aad, ciphertext) if len(dst) != len(ciphertext) { panic("crypto/aes: invalid destination plaintext size") } @@ -86,14 +87,14 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> } if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { - return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag) + return gcm_open_hw(&impl, dst, iv, aad, ciphertext, tag) } 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, &j0_enc, nonce) + init_ghash_ct64(ctx, &h, &j0, &j0_enc, iv) ct64.ghash(s[:], h[:], aad) gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false) @@ -112,7 +113,7 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> return ok } -// reset_ctr sanitizes the Context_GCM. The Context_GCM must be +// reset_gcm sanitizes the Context_GCM. The Context_GCM must be // re-initialized to be used again. reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { reset_impl(&ctx._impl) @@ -120,14 +121,14 @@ reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { } @(private = "file") -gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { +gcm_validate_common_slice_sizes :: proc(tag, iv, 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. - if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX { - panic("crypto/aes: invalid GCM nonce size") + // The specification supports IVs in the range [1, 2^64) bits. + if l := len(iv); l == 0 || u64(l) >= GCM_IV_SIZE_MAX { + panic("crypto/aes: invalid GCM IV size") } if aad_len := u64(len(aad)); aad_len > GCM_A_MAX { @@ -144,7 +145,7 @@ init_ghash_ct64 :: proc( h: ^[_aes.GHASH_KEY_SIZE]byte, j0: ^[_aes.GHASH_BLOCK_SIZE]byte, j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, - nonce: []byte, + iv: []byte, ) { impl := &ctx._impl.(ct64.Context) @@ -152,14 +153,14 @@ init_ghash_ct64 :: proc( ct64.encrypt_block(impl, h[:], h[:]) // Define a block, J0, as follows: - if l := len(nonce); l == GCM_NONCE_SIZE { + if l := len(iv); l == GCM_IV_SIZE { // if len(IV) = 96, then let J0 = IV || 0^31 || 1 - copy(j0[:], nonce) + copy(j0[:], iv) 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) + ct64.ghash(j0[:], h[:], iv) tmp: [_aes.GHASH_BLOCK_SIZE]byte endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) @@ -197,7 +198,7 @@ gctr_ct64 :: proc( s: ^[_aes.GHASH_BLOCK_SIZE]byte, src: []byte, h: ^[_aes.GHASH_KEY_SIZE]byte, - nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, + iv: ^[_aes.GHASH_BLOCK_SIZE]byte, is_seal: bool, ) #no_bounds_check { ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { @@ -208,14 +209,14 @@ gctr_ct64 :: proc( // Setup the counter blocks. tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, --- ctrs, blks: [ct64.STRIDE][]byte = ---, --- - ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1 + ctr := endian.unchecked_get_u32be(iv[GCM_IV_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[:GCM_NONCE_SIZE]) + copy(ctrs[i], iv[:GCM_IV_SIZE]) } impl := &ctx._impl.(ct64.Context) diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin index 7d32d4d96..ffd8ed642 100644 --- a/core/crypto/aes/aes_gcm_hw_intel.odin +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -10,12 +10,12 @@ import "core:mem" import "core:simd/x86" @(private) -gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, iv, 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) + init_ghash_hw(ctx, &h, &j0, &j0_enc, iv) // Note: Our GHASH implementation handles appending padding. hw_intel.ghash(s[:], h[:], aad) @@ -29,12 +29,12 @@ gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext } @(private) -gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, iv, 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) + init_ghash_hw(ctx, &h, &j0, &j0_enc, iv) hw_intel.ghash(s[:], h[:], aad) gctr_hw(ctx, dst, &s, ciphertext, &h, &j0, false) @@ -59,20 +59,20 @@ init_ghash_hw :: proc( h: ^[_aes.GHASH_KEY_SIZE]byte, j0: ^[_aes.GHASH_BLOCK_SIZE]byte, j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, - nonce: []byte, + iv: []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 l := len(iv); l == GCM_IV_SIZE { // if len(IV) = 96, then let J0 = IV || 0^31 || 1 - copy(j0[:], nonce) + copy(j0[:], iv) 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) + hw_intel.ghash(j0[:], h[:], iv) tmp: [_aes.GHASH_BLOCK_SIZE]byte endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) @@ -109,7 +109,7 @@ gctr_hw :: proc( s: ^[_aes.GHASH_BLOCK_SIZE]byte, src: []byte, h: ^[_aes.GHASH_KEY_SIZE]byte, - nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, + iv: ^[_aes.GHASH_BLOCK_SIZE]byte, is_seal: bool, ) #no_bounds_check { sks: [15]x86.__m128i = --- @@ -118,8 +118,8 @@ gctr_hw :: proc( } // Setup the counter block - ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(nonce)) - ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1 + ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(iv)) + ctr := endian.unchecked_get_u32be(iv[GCM_IV_SIZE:]) + 1 src, dst := src, dst diff --git a/core/crypto/aes/aes_impl.odin b/core/crypto/aes/aes_impl.odin index 03747f1fb..f26874809 100644 --- a/core/crypto/aes/aes_impl.odin +++ b/core/crypto/aes/aes_impl.odin @@ -10,6 +10,10 @@ Context_Impl :: union { Context_Impl_Hardware, } +// DEFAULT_IMPLEMENTATION is the implementation that will be used by +// default if possible. +DEFAULT_IMPLEMENTATION :: Implementation.Hardware + // Implementation is an AES implementation. Most callers will not need // to use this as the package will automatically select the most performant // implementation available (See `is_hardware_accelerated()`). diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin index 5361c6ef0..3557b1aae 100644 --- a/core/crypto/aes/aes_impl_hw_gen.odin +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -34,11 +34,11 @@ ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { } @(private) -gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, iv, aad, plaintext: []byte) { panic(ERR_HW_NOT_SUPPORTED) } @(private) -gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, iv, aad, ciphertext, tag: []byte) -> bool { panic(ERR_HW_NOT_SUPPORTED) } diff --git a/core/crypto/blake2b/blake2b.odin b/core/crypto/blake2b/blake2b.odin index 384c2ffea..74396b103 100644 --- a/core/crypto/blake2b/blake2b.odin +++ b/core/crypto/blake2b/blake2b.odin @@ -2,8 +2,8 @@ package blake2b implements the BLAKE2b hash algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc7693 -- https://www.blake2.net +- [[ https://datatracker.ietf.org/doc/html/rfc7693 ]] +- [[ https://www.blake2.net ]] */ package blake2b diff --git a/core/crypto/blake2s/blake2s.odin b/core/crypto/blake2s/blake2s.odin index 1ba9bef2d..339ddf027 100644 --- a/core/crypto/blake2s/blake2s.odin +++ b/core/crypto/blake2s/blake2s.odin @@ -2,8 +2,8 @@ package blake2s implements the BLAKE2s hash algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc7693 -- https://www.blake2.net/ +- [[ https://datatracker.ietf.org/doc/html/rfc7693 ]] +- [[ https://www.blake2.net/ ]] */ package blake2s diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index 73d3e1ea2..dfab2bc65 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -2,125 +2,72 @@ package chacha20 implements the ChaCha20 and XChaCha20 stream ciphers. See: -- https://datatracker.ietf.org/doc/html/rfc8439 -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/ +- [[ https://datatracker.ietf.org/doc/html/rfc8439 ]] +- [[ https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/ ]] */ package chacha20 import "core:bytes" -import "core:encoding/endian" -import "core:math/bits" +import "core:crypto/_chacha20" import "core:mem" // KEY_SIZE is the (X)ChaCha20 key size in bytes. -KEY_SIZE :: 32 -// NONCE_SIZE is the ChaCha20 nonce size in bytes. -NONCE_SIZE :: 12 -// XNONCE_SIZE is the XChaCha20 nonce size in bytes. -XNONCE_SIZE :: 24 - -@(private) -_MAX_CTR_IETF :: 0xffffffff - -@(private) -_BLOCK_SIZE :: 64 -@(private) -_STATE_SIZE_U32 :: 16 -@(private) -_ROUNDS :: 20 - -@(private) -_SIGMA_0: u32 : 0x61707865 -@(private) -_SIGMA_1: u32 : 0x3320646e -@(private) -_SIGMA_2: u32 : 0x79622d32 -@(private) -_SIGMA_3: u32 : 0x6b206574 +KEY_SIZE :: _chacha20.KEY_SIZE +// IV_SIZE is the ChaCha20 IV size in bytes. +IV_SIZE :: _chacha20.IV_SIZE +// XIV_SIZE is the XChaCha20 IV size in bytes. +XIV_SIZE :: _chacha20.XIV_SIZE // Context is a ChaCha20 or XChaCha20 instance. Context :: struct { - _s: [_STATE_SIZE_U32]u32, - _buffer: [_BLOCK_SIZE]byte, - _off: int, - _is_ietf_flavor: bool, - _is_initialized: bool, + _state: _chacha20.Context, + _impl: Implementation, } // init inititializes a Context for ChaCha20 or XChaCha20 with the provided -// key and nonce. -init :: proc(ctx: ^Context, key, nonce: []byte) { +// key and iv. +init :: proc(ctx: ^Context, key, iv: []byte, impl := DEFAULT_IMPLEMENTATION) { if len(key) != KEY_SIZE { - panic("crypto/chacha20: invalid ChaCha20 key size") + panic("crypto/chacha20: invalid (X)ChaCha20 key size") } - if n_len := len(nonce); n_len != NONCE_SIZE && n_len != XNONCE_SIZE { - panic("crypto/chacha20: invalid (X)ChaCha20 nonce size") + if l := len(iv); l != IV_SIZE && l != XIV_SIZE { + panic("crypto/chacha20: invalid (X)ChaCha20 IV size") } - k, n := key, nonce + k, n := key, iv - // Derive the XChaCha20 subkey and sub-nonce via HChaCha20. - is_xchacha := len(nonce) == XNONCE_SIZE + init_impl(ctx, impl) + + is_xchacha := len(iv) == XIV_SIZE if is_xchacha { - sub_key := ctx._buffer[:KEY_SIZE] - _hchacha20(sub_key, k, n) + sub_iv: [IV_SIZE]byte + sub_key := ctx._state._buffer[:KEY_SIZE] + hchacha20(sub_key, k, n, ctx._impl) k = sub_key - n = n[16:24] + copy(sub_iv[4:], n[16:]) + n = sub_iv[:] } - ctx._s[0] = _SIGMA_0 - ctx._s[1] = _SIGMA_1 - ctx._s[2] = _SIGMA_2 - ctx._s[3] = _SIGMA_3 - ctx._s[4] = endian.unchecked_get_u32le(k[0:4]) - ctx._s[5] = endian.unchecked_get_u32le(k[4:8]) - ctx._s[6] = endian.unchecked_get_u32le(k[8:12]) - ctx._s[7] = endian.unchecked_get_u32le(k[12:16]) - ctx._s[8] = endian.unchecked_get_u32le(k[16:20]) - ctx._s[9] = endian.unchecked_get_u32le(k[20:24]) - ctx._s[10] = endian.unchecked_get_u32le(k[24:28]) - ctx._s[11] = endian.unchecked_get_u32le(k[28:32]) - ctx._s[12] = 0 - if !is_xchacha { - ctx._s[13] = endian.unchecked_get_u32le(n[0:4]) - ctx._s[14] = endian.unchecked_get_u32le(n[4:8]) - ctx._s[15] = endian.unchecked_get_u32le(n[8:12]) - } else { - ctx._s[13] = 0 - ctx._s[14] = endian.unchecked_get_u32le(n[0:4]) - ctx._s[15] = endian.unchecked_get_u32le(n[4:8]) + _chacha20.init(&ctx._state, k, n, is_xchacha) + if is_xchacha { // The sub-key is stored in the keystream buffer. While // this will be overwritten in most circumstances, explicitly // clear it out early. - mem.zero_explicit(&ctx._buffer, KEY_SIZE) + mem.zero_explicit(&ctx._state._buffer, KEY_SIZE) } - - ctx._off = _BLOCK_SIZE - ctx._is_ietf_flavor = !is_xchacha - ctx._is_initialized = true } // seek seeks the (X)ChaCha20 stream counter to the specified block. seek :: proc(ctx: ^Context, block_nr: u64) { - assert(ctx._is_initialized) - - if ctx._is_ietf_flavor { - if block_nr > _MAX_CTR_IETF { - panic("crypto/chacha20: attempted to seek past maximum counter") - } - } else { - ctx._s[13] = u32(block_nr >> 32) - } - ctx._s[12] = u32(block_nr) - ctx._off = _BLOCK_SIZE + _chacha20.seek(&ctx._state, block_nr) } // xor_bytes XORs each byte in src with bytes taken from the (X)ChaCha20 // keystream, and writes the resulting output to dst. Dst and src MUST // alias exactly or not at all. xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { - assert(ctx._is_initialized) + assert(ctx._state._is_initialized) src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { @@ -131,12 +78,13 @@ xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { panic("crypto/chacha20: dst and src alias inexactly") } - for remaining := len(src); remaining > 0; { + st := &ctx._state + #no_bounds_check for remaining := len(src); remaining > 0; { // Process multiple blocks at once - if ctx._off == _BLOCK_SIZE { - if nr_blocks := remaining / _BLOCK_SIZE; nr_blocks > 0 { - direct_bytes := nr_blocks * _BLOCK_SIZE - _do_blocks(ctx, dst, src, nr_blocks) + if st._off == _chacha20.BLOCK_SIZE { + if nr_blocks := remaining / _chacha20.BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * _chacha20.BLOCK_SIZE + stream_blocks(ctx, dst, src, nr_blocks) remaining -= direct_bytes if remaining == 0 { return @@ -147,17 +95,17 @@ xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { // If there is a partial block, generate and buffer 1 block // worth of keystream. - _do_blocks(ctx, ctx._buffer[:], nil, 1) - ctx._off = 0 + stream_blocks(ctx, st._buffer[:], nil, 1) + st._off = 0 } // Process partial blocks from the buffered keystream. - to_xor := min(_BLOCK_SIZE - ctx._off, remaining) - buffered_keystream := ctx._buffer[ctx._off:] + to_xor := min(_chacha20.BLOCK_SIZE - st._off, remaining) + buffered_keystream := st._buffer[st._off:] for i := 0; i < to_xor; i = i + 1 { dst[i] = buffered_keystream[i] ~ src[i] } - ctx._off += to_xor + st._off += to_xor dst = dst[to_xor:] src = src[to_xor:] remaining -= to_xor @@ -166,15 +114,15 @@ xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { // keystream_bytes fills dst with the raw (X)ChaCha20 keystream output. keystream_bytes :: proc(ctx: ^Context, dst: []byte) { - assert(ctx._is_initialized) + assert(ctx._state._is_initialized) - dst := dst - for remaining := len(dst); remaining > 0; { + dst, st := dst, &ctx._state + #no_bounds_check for remaining := len(dst); remaining > 0; { // Process multiple blocks at once - if ctx._off == _BLOCK_SIZE { - if nr_blocks := remaining / _BLOCK_SIZE; nr_blocks > 0 { - direct_bytes := nr_blocks * _BLOCK_SIZE - _do_blocks(ctx, dst, nil, nr_blocks) + if st._off == _chacha20.BLOCK_SIZE { + if nr_blocks := remaining / _chacha20.BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * _chacha20.BLOCK_SIZE + stream_blocks(ctx, dst, nil, nr_blocks) remaining -= direct_bytes if remaining == 0 { return @@ -184,15 +132,15 @@ keystream_bytes :: proc(ctx: ^Context, dst: []byte) { // If there is a partial block, generate and buffer 1 block // worth of keystream. - _do_blocks(ctx, ctx._buffer[:], nil, 1) - ctx._off = 0 + stream_blocks(ctx, st._buffer[:], nil, 1) + st._off = 0 } // Process partial blocks from the buffered keystream. - to_copy := min(_BLOCK_SIZE - ctx._off, remaining) - buffered_keystream := ctx._buffer[ctx._off:] + to_copy := min(_chacha20.BLOCK_SIZE - st._off, remaining) + buffered_keystream := st._buffer[st._off:] copy(dst[:to_copy], buffered_keystream[:to_copy]) - ctx._off += to_copy + st._off += to_copy dst = dst[to_copy:] remaining -= to_copy } @@ -201,366 +149,5 @@ keystream_bytes :: proc(ctx: ^Context, dst: []byte) { // reset sanitizes the Context. The Context must be re-initialized to // be used again. reset :: proc(ctx: ^Context) { - mem.zero_explicit(&ctx._s, size_of(ctx._s)) - mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) - - ctx._is_initialized = false -} - -@(private) -_do_blocks :: proc(ctx: ^Context, dst, src: []byte, nr_blocks: int) { - // Enforce the maximum consumed keystream per nonce. - // - // While all modern "standard" definitions of ChaCha20 use - // the IETF 32-bit counter, for XChaCha20 most common - // implementations allow for a 64-bit counter. - // - // Honestly, the answer here is "use a MRAE primitive", but - // go with common practice in the case of XChaCha20. - if ctx._is_ietf_flavor { - if u64(ctx._s[12]) + u64(nr_blocks) > 0xffffffff { - panic("crypto/chacha20: maximum ChaCha20 keystream per nonce reached") - } - } else { - ctr := (u64(ctx._s[13]) << 32) | u64(ctx._s[12]) - if _, carry := bits.add_u64(ctr, u64(nr_blocks), 0); carry != 0 { - panic("crypto/chacha20: maximum XChaCha20 keystream per nonce reached") - } - } - - dst, src := dst, src - x := &ctx._s - for n := 0; n < nr_blocks; n = n + 1 { - x0, x1, x2, x3 := _SIGMA_0, _SIGMA_1, _SIGMA_2, _SIGMA_3 - x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15] - - for i := _ROUNDS; i > 0; i = i - 2 { - // Even when forcing inlining manually inlining all of - // these is decently faster. - - // quarterround(x, 0, 4, 8, 12) - x0 += x4 - x12 ~= x0 - x12 = bits.rotate_left32(x12, 16) - x8 += x12 - x4 ~= x8 - x4 = bits.rotate_left32(x4, 12) - x0 += x4 - x12 ~= x0 - x12 = bits.rotate_left32(x12, 8) - x8 += x12 - x4 ~= x8 - x4 = bits.rotate_left32(x4, 7) - - // quarterround(x, 1, 5, 9, 13) - x1 += x5 - x13 ~= x1 - x13 = bits.rotate_left32(x13, 16) - x9 += x13 - x5 ~= x9 - x5 = bits.rotate_left32(x5, 12) - x1 += x5 - x13 ~= x1 - x13 = bits.rotate_left32(x13, 8) - x9 += x13 - x5 ~= x9 - x5 = bits.rotate_left32(x5, 7) - - // quarterround(x, 2, 6, 10, 14) - x2 += x6 - x14 ~= x2 - x14 = bits.rotate_left32(x14, 16) - x10 += x14 - x6 ~= x10 - x6 = bits.rotate_left32(x6, 12) - x2 += x6 - x14 ~= x2 - x14 = bits.rotate_left32(x14, 8) - x10 += x14 - x6 ~= x10 - x6 = bits.rotate_left32(x6, 7) - - // quarterround(x, 3, 7, 11, 15) - x3 += x7 - x15 ~= x3 - x15 = bits.rotate_left32(x15, 16) - x11 += x15 - x7 ~= x11 - x7 = bits.rotate_left32(x7, 12) - x3 += x7 - x15 ~= x3 - x15 = bits.rotate_left32(x15, 8) - x11 += x15 - x7 ~= x11 - x7 = bits.rotate_left32(x7, 7) - - // quarterround(x, 0, 5, 10, 15) - x0 += x5 - x15 ~= x0 - x15 = bits.rotate_left32(x15, 16) - x10 += x15 - x5 ~= x10 - x5 = bits.rotate_left32(x5, 12) - x0 += x5 - x15 ~= x0 - x15 = bits.rotate_left32(x15, 8) - x10 += x15 - x5 ~= x10 - x5 = bits.rotate_left32(x5, 7) - - // quarterround(x, 1, 6, 11, 12) - x1 += x6 - x12 ~= x1 - x12 = bits.rotate_left32(x12, 16) - x11 += x12 - x6 ~= x11 - x6 = bits.rotate_left32(x6, 12) - x1 += x6 - x12 ~= x1 - x12 = bits.rotate_left32(x12, 8) - x11 += x12 - x6 ~= x11 - x6 = bits.rotate_left32(x6, 7) - - // quarterround(x, 2, 7, 8, 13) - x2 += x7 - x13 ~= x2 - x13 = bits.rotate_left32(x13, 16) - x8 += x13 - x7 ~= x8 - x7 = bits.rotate_left32(x7, 12) - x2 += x7 - x13 ~= x2 - x13 = bits.rotate_left32(x13, 8) - x8 += x13 - x7 ~= x8 - x7 = bits.rotate_left32(x7, 7) - - // quarterround(x, 3, 4, 9, 14) - x3 += x4 - x14 ~= x3 - x14 = bits.rotate_left32(x14, 16) - x9 += x14 - x4 ~= x9 - x4 = bits.rotate_left32(x4, 12) - x3 += x4 - x14 ~= x3 - x14 = bits.rotate_left32(x14, 8) - x9 += x14 - x4 ~= x9 - x4 = bits.rotate_left32(x4, 7) - } - - x0 += _SIGMA_0 - x1 += _SIGMA_1 - x2 += _SIGMA_2 - x3 += _SIGMA_3 - x4 += x[4] - x5 += x[5] - x6 += x[6] - x7 += x[7] - x8 += x[8] - x9 += x[9] - x10 += x[10] - x11 += x[11] - x12 += x[12] - x13 += x[13] - x14 += x[14] - x15 += x[15] - - // While the "correct" answer to getting more performance out of - // this is "use vector operations", support for that is currently - // a work in progress/to be designed. - // - // In the meantime: - // - The caller(s) ensure that src/dst are valid. - // - The compiler knows if the target is picky about alignment. - - #no_bounds_check { - if src != nil { - endian.unchecked_put_u32le(dst[0:4], endian.unchecked_get_u32le(src[0:4]) ~ x0) - endian.unchecked_put_u32le(dst[4:8], endian.unchecked_get_u32le(src[4:8]) ~ x1) - endian.unchecked_put_u32le(dst[8:12], endian.unchecked_get_u32le(src[8:12]) ~ x2) - endian.unchecked_put_u32le(dst[12:16], endian.unchecked_get_u32le(src[12:16]) ~ x3) - endian.unchecked_put_u32le(dst[16:20], endian.unchecked_get_u32le(src[16:20]) ~ x4) - endian.unchecked_put_u32le(dst[20:24], endian.unchecked_get_u32le(src[20:24]) ~ x5) - endian.unchecked_put_u32le(dst[24:28], endian.unchecked_get_u32le(src[24:28]) ~ x6) - endian.unchecked_put_u32le(dst[28:32], endian.unchecked_get_u32le(src[28:32]) ~ x7) - endian.unchecked_put_u32le(dst[32:36], endian.unchecked_get_u32le(src[32:36]) ~ x8) - endian.unchecked_put_u32le(dst[36:40], endian.unchecked_get_u32le(src[36:40]) ~ x9) - endian.unchecked_put_u32le(dst[40:44], endian.unchecked_get_u32le(src[40:44]) ~ x10) - endian.unchecked_put_u32le(dst[44:48], endian.unchecked_get_u32le(src[44:48]) ~ x11) - endian.unchecked_put_u32le(dst[48:52], endian.unchecked_get_u32le(src[48:52]) ~ x12) - endian.unchecked_put_u32le(dst[52:56], endian.unchecked_get_u32le(src[52:56]) ~ x13) - endian.unchecked_put_u32le(dst[56:60], endian.unchecked_get_u32le(src[56:60]) ~ x14) - endian.unchecked_put_u32le(dst[60:64], endian.unchecked_get_u32le(src[60:64]) ~ x15) - src = src[_BLOCK_SIZE:] - } else { - endian.unchecked_put_u32le(dst[0:4], x0) - endian.unchecked_put_u32le(dst[4:8], x1) - endian.unchecked_put_u32le(dst[8:12], x2) - endian.unchecked_put_u32le(dst[12:16], x3) - endian.unchecked_put_u32le(dst[16:20], x4) - endian.unchecked_put_u32le(dst[20:24], x5) - endian.unchecked_put_u32le(dst[24:28], x6) - endian.unchecked_put_u32le(dst[28:32], x7) - endian.unchecked_put_u32le(dst[32:36], x8) - endian.unchecked_put_u32le(dst[36:40], x9) - endian.unchecked_put_u32le(dst[40:44], x10) - endian.unchecked_put_u32le(dst[44:48], x11) - endian.unchecked_put_u32le(dst[48:52], x12) - endian.unchecked_put_u32le(dst[52:56], x13) - endian.unchecked_put_u32le(dst[56:60], x14) - endian.unchecked_put_u32le(dst[60:64], x15) - } - dst = dst[_BLOCK_SIZE:] - } - - // Increment the counter. Overflow checking is done upon - // entry into the routine, so a 64-bit increment safely - // covers both cases. - new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + 1 - x[12] = u32(new_ctr) - x[13] = u32(new_ctr >> 32) - } -} - -@(private) -_hchacha20 :: proc "contextless" (dst, key, nonce: []byte) { - x0, x1, x2, x3 := _SIGMA_0, _SIGMA_1, _SIGMA_2, _SIGMA_3 - x4 := endian.unchecked_get_u32le(key[0:4]) - x5 := endian.unchecked_get_u32le(key[4:8]) - x6 := endian.unchecked_get_u32le(key[8:12]) - x7 := endian.unchecked_get_u32le(key[12:16]) - x8 := endian.unchecked_get_u32le(key[16:20]) - x9 := endian.unchecked_get_u32le(key[20:24]) - x10 := endian.unchecked_get_u32le(key[24:28]) - x11 := endian.unchecked_get_u32le(key[28:32]) - x12 := endian.unchecked_get_u32le(nonce[0:4]) - x13 := endian.unchecked_get_u32le(nonce[4:8]) - x14 := endian.unchecked_get_u32le(nonce[8:12]) - x15 := endian.unchecked_get_u32le(nonce[12:16]) - - for i := _ROUNDS; i > 0; i = i - 2 { - // quarterround(x, 0, 4, 8, 12) - x0 += x4 - x12 ~= x0 - x12 = bits.rotate_left32(x12, 16) - x8 += x12 - x4 ~= x8 - x4 = bits.rotate_left32(x4, 12) - x0 += x4 - x12 ~= x0 - x12 = bits.rotate_left32(x12, 8) - x8 += x12 - x4 ~= x8 - x4 = bits.rotate_left32(x4, 7) - - // quarterround(x, 1, 5, 9, 13) - x1 += x5 - x13 ~= x1 - x13 = bits.rotate_left32(x13, 16) - x9 += x13 - x5 ~= x9 - x5 = bits.rotate_left32(x5, 12) - x1 += x5 - x13 ~= x1 - x13 = bits.rotate_left32(x13, 8) - x9 += x13 - x5 ~= x9 - x5 = bits.rotate_left32(x5, 7) - - // quarterround(x, 2, 6, 10, 14) - x2 += x6 - x14 ~= x2 - x14 = bits.rotate_left32(x14, 16) - x10 += x14 - x6 ~= x10 - x6 = bits.rotate_left32(x6, 12) - x2 += x6 - x14 ~= x2 - x14 = bits.rotate_left32(x14, 8) - x10 += x14 - x6 ~= x10 - x6 = bits.rotate_left32(x6, 7) - - // quarterround(x, 3, 7, 11, 15) - x3 += x7 - x15 ~= x3 - x15 = bits.rotate_left32(x15, 16) - x11 += x15 - x7 ~= x11 - x7 = bits.rotate_left32(x7, 12) - x3 += x7 - x15 ~= x3 - x15 = bits.rotate_left32(x15, 8) - x11 += x15 - x7 ~= x11 - x7 = bits.rotate_left32(x7, 7) - - // quarterround(x, 0, 5, 10, 15) - x0 += x5 - x15 ~= x0 - x15 = bits.rotate_left32(x15, 16) - x10 += x15 - x5 ~= x10 - x5 = bits.rotate_left32(x5, 12) - x0 += x5 - x15 ~= x0 - x15 = bits.rotate_left32(x15, 8) - x10 += x15 - x5 ~= x10 - x5 = bits.rotate_left32(x5, 7) - - // quarterround(x, 1, 6, 11, 12) - x1 += x6 - x12 ~= x1 - x12 = bits.rotate_left32(x12, 16) - x11 += x12 - x6 ~= x11 - x6 = bits.rotate_left32(x6, 12) - x1 += x6 - x12 ~= x1 - x12 = bits.rotate_left32(x12, 8) - x11 += x12 - x6 ~= x11 - x6 = bits.rotate_left32(x6, 7) - - // quarterround(x, 2, 7, 8, 13) - x2 += x7 - x13 ~= x2 - x13 = bits.rotate_left32(x13, 16) - x8 += x13 - x7 ~= x8 - x7 = bits.rotate_left32(x7, 12) - x2 += x7 - x13 ~= x2 - x13 = bits.rotate_left32(x13, 8) - x8 += x13 - x7 ~= x8 - x7 = bits.rotate_left32(x7, 7) - - // quarterround(x, 3, 4, 9, 14) - x3 += x4 - x14 ~= x3 - x14 = bits.rotate_left32(x14, 16) - x9 += x14 - x4 ~= x9 - x4 = bits.rotate_left32(x4, 12) - x3 += x4 - x14 ~= x3 - x14 = bits.rotate_left32(x14, 8) - x9 += x14 - x4 ~= x9 - x4 = bits.rotate_left32(x4, 7) - } - - endian.unchecked_put_u32le(dst[0:4], x0) - endian.unchecked_put_u32le(dst[4:8], x1) - endian.unchecked_put_u32le(dst[8:12], x2) - endian.unchecked_put_u32le(dst[12:16], x3) - endian.unchecked_put_u32le(dst[16:20], x12) - endian.unchecked_put_u32le(dst[20:24], x13) - endian.unchecked_put_u32le(dst[24:28], x14) - endian.unchecked_put_u32le(dst[28:32], x15) + _chacha20.reset(&ctx._state) } diff --git a/core/crypto/chacha20/chacha20_impl.odin b/core/crypto/chacha20/chacha20_impl.odin new file mode 100644 index 000000000..be2ee06b4 --- /dev/null +++ b/core/crypto/chacha20/chacha20_impl.odin @@ -0,0 +1,56 @@ +package chacha20 + +import "base:intrinsics" +import "core:crypto/_chacha20/ref" +import "core:crypto/_chacha20/simd128" +import "core:crypto/_chacha20/simd256" + +// DEFAULT_IMPLEMENTATION is the implementation that will be used by +// default if possible. +DEFAULT_IMPLEMENTATION :: Implementation.Simd256 + +// Implementation is a ChaCha20 implementation. Most callers will not need +// to use this as the package will automatically select the most performant +// implementation available. +Implementation :: enum { + Portable, + Simd128, + Simd256, +} + +@(private) +init_impl :: proc(ctx: ^Context, impl: Implementation) { + impl := impl + if impl == .Simd256 && !simd256.is_performant() { + impl = .Simd128 + } + if impl == .Simd128 && !simd128.is_performant() { + impl = .Portable + } + + ctx._impl = impl +} + +@(private) +stream_blocks :: proc(ctx: ^Context, dst, src: []byte, nr_blocks: int) { + switch ctx._impl { + case .Simd256: + simd256.stream_blocks(&ctx._state, dst, src, nr_blocks) + case .Simd128: + simd128.stream_blocks(&ctx._state, dst, src, nr_blocks) + case .Portable: + ref.stream_blocks(&ctx._state, dst, src, nr_blocks) + } +} + +@(private) +hchacha20 :: proc "contextless" (dst, key, iv: []byte, impl: Implementation) { + switch impl { + case .Simd256: + simd256.hchacha20(dst, key, iv) + case .Simd128: + simd128.hchacha20(dst, key, iv) + case .Portable: + ref.hchacha20(dst, key, iv) + } +} diff --git a/core/crypto/chacha20poly1305/chacha20poly1305.odin b/core/crypto/chacha20poly1305/chacha20poly1305.odin index 7fc112d0d..3de2532dd 100644 --- a/core/crypto/chacha20poly1305/chacha20poly1305.odin +++ b/core/crypto/chacha20poly1305/chacha20poly1305.odin @@ -1,9 +1,11 @@ /* -package chacha20poly1305 implements the AEAD_CHACHA20_POLY1305 Authenticated -Encryption with Additional Data algorithm. +package chacha20poly1305 implements the AEAD_CHACHA20_POLY1305 and +AEAD_XChaCha20_Poly1305 Authenticated Encryption with Additional Data +algorithms. See: -- https://www.rfc-editor.org/rfc/rfc8439 +- [[ https://www.rfc-editor.org/rfc/rfc8439 ]] +- [[ https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03 ]] */ package chacha20poly1305 @@ -15,8 +17,10 @@ import "core:mem" // KEY_SIZE is the chacha20poly1305 key size in bytes. KEY_SIZE :: chacha20.KEY_SIZE -// NONCE_SIZE is the chacha20poly1305 nonce size in bytes. -NONCE_SIZE :: chacha20.NONCE_SIZE +// IV_SIZE is the chacha20poly1305 IV size in bytes. +IV_SIZE :: chacha20.IV_SIZE +// XIV_SIZE is the xchacha20poly1305 IV size in bytes. +XIV_SIZE :: chacha20.XIV_SIZE // TAG_SIZE is the chacha20poly1305 tag size in bytes. TAG_SIZE :: poly1305.TAG_SIZE @@ -24,15 +28,13 @@ TAG_SIZE :: poly1305.TAG_SIZE _P_MAX :: 64 * 0xffffffff // 64 * (2^32-1) @(private) -_validate_common_slice_sizes :: proc (tag, key, nonce, aad, text: []byte) { +_validate_common_slice_sizes :: proc (tag, iv, aad, text: []byte, is_xchacha: bool) { if len(tag) != TAG_SIZE { panic("crypto/chacha20poly1305: invalid destination tag size") } - if len(key) != KEY_SIZE { - panic("crypto/chacha20poly1305: invalid key size") - } - if len(nonce) != NONCE_SIZE { - panic("crypto/chacha20poly1305: invalid nonce size") + expected_iv_len := is_xchacha ? XIV_SIZE : IV_SIZE + if len(iv) != expected_iv_len { + panic("crypto/chacha20poly1305: invalid IV size") } #assert(size_of(int) == 8 || size_of(int) <= 4) @@ -59,18 +61,52 @@ _update_mac_pad16 :: #force_inline proc (ctx: ^poly1305.Context, x_len: int) { } } -// encrypt encrypts the plaintext and authenticates the aad and ciphertext, -// with the provided key and nonce, stores the output in ciphertext and tag. -encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { - _validate_common_slice_sizes(tag, key, nonce, aad, plaintext) +// Context is a keyed (X)Chacha20Poly1305 instance. +Context :: struct { + _key: [KEY_SIZE]byte, + _impl: chacha20.Implementation, + _is_xchacha: bool, + _is_initialized: bool, +} + +// init initializes a Context with the provided key, for AEAD_CHACHA20_POLY1305. +init :: proc(ctx: ^Context, key: []byte, impl := chacha20.DEFAULT_IMPLEMENTATION) { + if len(key) != KEY_SIZE { + panic("crypto/chacha20poly1305: invalid key size") + } + + copy(ctx._key[:], key) + ctx._impl = impl + ctx._is_xchacha = false + ctx._is_initialized = true +} + +// init_xchacha initializes a Context with the provided key, for +// AEAD_XChaCha20_Poly1305. +// +// Note: While there are multiple definitions of XChaCha20-Poly1305 +// this sticks to the IETF draft and uses a 32-bit counter. +init_xchacha :: proc(ctx: ^Context, key: []byte, impl := chacha20.DEFAULT_IMPLEMENTATION) { + init(ctx, key, impl) + ctx._is_xchacha = true +} + +// seal encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + ciphertext := dst + _validate_common_slice_sizes(tag, iv, aad, plaintext, ctx._is_xchacha) if len(ciphertext) != len(plaintext) { panic("crypto/chacha20poly1305: invalid destination ciphertext size") } stream_ctx: chacha20.Context = --- - chacha20.init(&stream_ctx, key, nonce) + chacha20.init(&stream_ctx, ctx._key[:],iv, ctx._impl) + stream_ctx._state._is_ietf_flavor = true - // otk = poly1305_key_gen(key, nonce) + // otk = poly1305_key_gen(key, iv) otk: [poly1305.KEY_SIZE]byte = --- chacha20.keystream_bytes(&stream_ctx, otk[:]) mac_ctx: poly1305.Context = --- @@ -87,7 +123,7 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { poly1305.update(&mac_ctx, aad) _update_mac_pad16(&mac_ctx, aad_len) - // ciphertext = chacha20_encrypt(key, 1, nonce, plaintext) + // ciphertext = chacha20_encrypt(key, 1, iv, plaintext) chacha20.seek(&stream_ctx, 1) chacha20.xor_bytes(&stream_ctx, ciphertext, plaintext) chacha20.reset(&stream_ctx) // Don't need the stream context anymore. @@ -107,13 +143,16 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context. } -// decrypt authenticates the aad and ciphertext, and decrypts the ciphertext, -// with the provided key, nonce, and tag, and stores the output in plaintext, -// returning true iff the authentication was successful. +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. // -// If authentication fails, the destination plaintext buffer will be zeroed. -decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { - _validate_common_slice_sizes(tag, key, nonce, aad, ciphertext) +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + plaintext := dst + _validate_common_slice_sizes(tag, iv, aad, ciphertext, ctx._is_xchacha) if len(ciphertext) != len(plaintext) { panic("crypto/chacha20poly1305: invalid destination plaintext size") } @@ -123,9 +162,10 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { // points where needed. stream_ctx: chacha20.Context = --- - chacha20.init(&stream_ctx, key, nonce) + chacha20.init(&stream_ctx, ctx._key[:], iv, ctx._impl) + stream_ctx._state._is_ietf_flavor = true - // otk = poly1305_key_gen(key, nonce) + // otk = poly1305_key_gen(key, iv) otk: [poly1305.KEY_SIZE]byte = --- chacha20.keystream_bytes(&stream_ctx, otk[:]) defer chacha20.reset(&stream_ctx) @@ -160,9 +200,17 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { return false } - // plaintext = chacha20_decrypt(key, 1, nonce, ciphertext) + // plaintext = chacha20_decrypt(key, 1, iv, ciphertext) chacha20.seek(&stream_ctx, 1) chacha20.xor_bytes(&stream_ctx, plaintext, ciphertext) return true } + +// reset sanitizes the Context. The Context must be +// re-initialized to be used again. +reset :: proc "contextless" (ctx: ^Context) { + mem.zero_explicit(&ctx._key, len(ctx._key)) + ctx._is_xchacha = false + ctx._is_initialized = false +} diff --git a/core/crypto/ed25519/ed25519.odin b/core/crypto/ed25519/ed25519.odin index 86da35669..460a19563 100644 --- a/core/crypto/ed25519/ed25519.odin +++ b/core/crypto/ed25519/ed25519.odin @@ -2,9 +2,9 @@ package ed25519 implements the Ed25519 EdDSA signature algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc8032 -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf -- https://eprint.iacr.org/2020/1244.pdf +- [[ https://datatracker.ietf.org/doc/html/rfc8032 ]] +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf ]] +- [[ https://eprint.iacr.org/2020/1244.pdf ]] */ package ed25519 @@ -21,7 +21,7 @@ PUBLIC_KEY_SIZE :: 32 SIGNATURE_SIZE :: 64 @(private) -NONCE_SIZE :: 32 +HDIGEST2_SIZE :: 32 // Private_Key is an Ed25519 private key. Private_Key :: struct { @@ -33,7 +33,7 @@ Private_Key :: struct { // See: https://github.com/MystenLabs/ed25519-unsafe-libs _b: [PRIVATE_KEY_SIZE]byte, _s: grp.Scalar, - _nonce: [NONCE_SIZE]byte, + _hdigest2: [HDIGEST2_SIZE]byte, _pub_key: Public_Key, _is_initialized: bool, } @@ -63,7 +63,7 @@ private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool { sha2.final(&ctx, h_bytes[:]) copy(priv_key._b[:], b) - copy(priv_key._nonce[:], h_bytes[32:]) + copy(priv_key._hdigest2[:], h_bytes[32:]) grp.sc_set_bytes_rfc8032(&priv_key._s, h_bytes[:32]) // Derive the corresponding public key. @@ -116,7 +116,7 @@ sign :: proc(priv_key: ^Private_Key, msg, sig: []byte) { ctx: sha2.Context_512 = --- digest_bytes: [sha2.DIGEST_SIZE_512]byte = --- sha2.init_512(&ctx) - sha2.update(&ctx, priv_key._nonce[:]) + sha2.update(&ctx, priv_key._hdigest2[:]) sha2.update(&ctx, msg) sha2.final(&ctx, digest_bytes[:]) diff --git a/core/crypto/hash/doc.odin b/core/crypto/hash/doc.odin index d50908b94..1dfd97de2 100644 --- a/core/crypto/hash/doc.odin +++ b/core/crypto/hash/doc.odin @@ -17,46 +17,44 @@ accomplish common tasks. A third optional boolean parameter controls if the file is streamed (default), or or read at once. -```odin -package hash_example +Example: + package hash_example -import "core:crypto/hash" + import "core:crypto/hash" -main :: proc() { - input := "Feed the fire." + main :: proc() { + input := "Feed the fire." - // Compute the digest, using the high level API. - returned_digest := hash.hash(hash.Algorithm.SHA512_256, input) - defer delete(returned_digest) + // Compute the digest, using the high level API. + returned_digest := hash.hash(hash.Algorithm.SHA512_256, input) + defer delete(returned_digest) - // Variant that takes a destination buffer, instead of returning - // the digest. - digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.BLAKE2B]) // @note: Destination buffer has to be at least as big as the digest size of the hash. - defer delete(digest) - hash.hash(hash.Algorithm.BLAKE2B, input, digest) -} -``` + // Variant that takes a destination buffer, instead of returning + // the digest. + digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.BLAKE2B]) // @note: Destination buffer has to be at least as big as the digest size of the hash. + defer delete(digest) + hash.hash(hash.Algorithm.BLAKE2B, input, digest) + } A generic low level API is provided supporting the init/update/final interface that is typical with cryptographic hash function implementations. -```odin -package hash_example +Example: + package hash_example -import "core:crypto/hash" + import "core:crypto/hash" -main :: proc() { - input := "Let the cinders burn." + main :: proc() { + input := "Let the cinders burn." - // Compute the digest, using the low level API. - ctx: hash.Context - digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.SHA3_512]) - defer delete(digest) + // Compute the digest, using the low level API. + ctx: hash.Context + digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.SHA3_512]) + defer delete(digest) - hash.init(&ctx, hash.Algorithm.SHA3_512) - hash.update(&ctx, transmute([]byte)input) - hash.final(&ctx, digest) -} -``` + hash.init(&ctx, hash.Algorithm.SHA3_512) + hash.update(&ctx, transmute([]byte)input) + hash.final(&ctx, digest) + } */ -package crypto_hash \ No newline at end of file +package crypto_hash diff --git a/core/crypto/hash/hash.odin b/core/crypto/hash/hash.odin index e4b3d4be1..f7671270a 100644 --- a/core/crypto/hash/hash.odin +++ b/core/crypto/hash/hash.odin @@ -28,20 +28,26 @@ hash_bytes :: proc(algorithm: Algorithm, data: []byte, allocator := context.allo // hash_string_to_buffer will hash the given input and assign the // computed digest to the third parameter. It requires that the -// destination buffer is at least as big as the digest size. -hash_string_to_buffer :: proc(algorithm: Algorithm, data: string, hash: []byte) { - hash_bytes_to_buffer(algorithm, transmute([]byte)(data), hash) +// destination buffer is at least as big as the digest size. The +// provided destination buffer is returned to match the behavior of +// `hash_string`. +hash_string_to_buffer :: proc(algorithm: Algorithm, data: string, hash: []byte) -> []byte { + return hash_bytes_to_buffer(algorithm, transmute([]byte)(data), hash) } // hash_bytes_to_buffer will hash the given input and write the // computed digest into the third parameter. It requires that the -// destination buffer is at least as big as the digest size. -hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) { +// destination buffer is at least as big as the digest size. The +// provided destination buffer is returned to match the behavior of +// `hash_bytes`. +hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) -> []byte { ctx: Context init(&ctx, algorithm) update(&ctx, data) final(&ctx, hash) + + return hash } // hash_stream will incrementally fully consume a stream, and return the diff --git a/core/crypto/hkdf/hkdf.odin b/core/crypto/hkdf/hkdf.odin index 2ac67476e..bffe09eff 100644 --- a/core/crypto/hkdf/hkdf.odin +++ b/core/crypto/hkdf/hkdf.odin @@ -2,7 +2,7 @@ package hkdf implements the HKDF HMAC-based Extract-and-Expand Key Derivation Function. -See: https://www.rfc-editor.org/rfc/rfc5869 +See: [[ https://www.rfc-editor.org/rfc/rfc5869 ]] */ package hkdf diff --git a/core/crypto/hmac/hmac.odin b/core/crypto/hmac/hmac.odin index 6aac8fca7..4813a9938 100644 --- a/core/crypto/hmac/hmac.odin +++ b/core/crypto/hmac/hmac.odin @@ -2,7 +2,7 @@ package hmac implements the HMAC MAC algorithm. See: -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf ]] */ package hmac diff --git a/core/crypto/kmac/kmac.odin b/core/crypto/kmac/kmac.odin index 711f459b3..e8bf42946 100644 --- a/core/crypto/kmac/kmac.odin +++ b/core/crypto/kmac/kmac.odin @@ -2,7 +2,7 @@ package kmac implements the KMAC MAC algorithm. See: -- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] */ package kmac diff --git a/core/crypto/legacy/md5/md5.odin b/core/crypto/legacy/md5/md5.odin index c744a9bcf..28b47e0b3 100644 --- a/core/crypto/legacy/md5/md5.odin +++ b/core/crypto/legacy/md5/md5.odin @@ -5,8 +5,8 @@ WARNING: The MD5 algorithm is known to be insecure and should only be used for interoperating with legacy applications. See: -- https://eprint.iacr.org/2005/075 -- https://datatracker.ietf.org/doc/html/rfc1321 +- [[ https://eprint.iacr.org/2005/075 ]] +- [[ https://datatracker.ietf.org/doc/html/rfc1321 ]] */ package md5 diff --git a/core/crypto/legacy/sha1/sha1.odin b/core/crypto/legacy/sha1/sha1.odin index 8c6e59901..1025ecb5b 100644 --- a/core/crypto/legacy/sha1/sha1.odin +++ b/core/crypto/legacy/sha1/sha1.odin @@ -5,9 +5,9 @@ WARNING: The SHA1 algorithm is known to be insecure and should only be used for interoperating with legacy applications. See: -- https://eprint.iacr.org/2017/190 -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf -- https://datatracker.ietf.org/doc/html/rfc3174 +- [[ https://eprint.iacr.org/2017/190 ]] +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf ]] +- [[ https://datatracker.ietf.org/doc/html/rfc3174 ]] */ package sha1 diff --git a/core/crypto/pbkdf2/pbkdf2.odin b/core/crypto/pbkdf2/pbkdf2.odin index 20e490135..8bb5cb73e 100644 --- a/core/crypto/pbkdf2/pbkdf2.odin +++ b/core/crypto/pbkdf2/pbkdf2.odin @@ -1,7 +1,7 @@ /* package pbkdf2 implements the PBKDF2 password-based key derivation function. -See: https://www.rfc-editor.org/rfc/rfc2898 +See: [[ https://www.rfc-editor.org/rfc/rfc2898 ]] */ package pbkdf2 diff --git a/core/crypto/poly1305/poly1305.odin b/core/crypto/poly1305/poly1305.odin index 443917a6a..ea0e6c907 100644 --- a/core/crypto/poly1305/poly1305.odin +++ b/core/crypto/poly1305/poly1305.odin @@ -2,7 +2,7 @@ package poly1305 implements the Poly1305 one-time MAC algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc8439 +- [[ https://datatracker.ietf.org/doc/html/rfc8439 ]] */ package poly1305 diff --git a/core/crypto/ristretto255/ristretto255.odin b/core/crypto/ristretto255/ristretto255.odin index 3a2307da0..7b0944e33 100644 --- a/core/crypto/ristretto255/ristretto255.odin +++ b/core/crypto/ristretto255/ristretto255.odin @@ -2,7 +2,7 @@ package ristretto255 implement the ristretto255 prime-order group. See: -- https://www.rfc-editor.org/rfc/rfc9496 +- [[ https://www.rfc-editor.org/rfc/rfc9496 ]] */ package ristretto255 diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index 2128e3950..4230851ab 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -2,8 +2,8 @@ package sha2 implements the SHA2 hash algorithm family. See: -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf -- https://datatracker.ietf.org/doc/html/rfc3874 +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf ]] +- [[ https://datatracker.ietf.org/doc/html/rfc3874 ]] */ package sha2 diff --git a/core/crypto/sha3/sha3.odin b/core/crypto/sha3/sha3.odin index 78057f1ca..3b7bdedd7 100644 --- a/core/crypto/sha3/sha3.odin +++ b/core/crypto/sha3/sha3.odin @@ -6,7 +6,7 @@ pre-standardization Keccak algorithm is required, it can be found in crypto/legacy/keccak. See: -- https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf ]] */ package sha3 diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 4160f4cf9..e20795b43 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -4,8 +4,8 @@ package shake implements the SHAKE and cSHAKE XOF algorithm families. The SHA3 hash algorithm can be found in the crypto/sha3. See: -- https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf -- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf ]] +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] */ package shake diff --git a/core/crypto/siphash/siphash.odin b/core/crypto/siphash/siphash.odin index c51e38ab0..c145ab3f0 100644 --- a/core/crypto/siphash/siphash.odin +++ b/core/crypto/siphash/siphash.odin @@ -1,3 +1,12 @@ +/* +package siphash Implements the SipHash hashing algorithm. + +Use the specific procedures for a certain setup. The generic procedures will default to Siphash 2-4. + +See: +- [[ https://github.com/veorq/SipHash ]] +- [[ https://www.aumasson.jp/siphash/siphash.pdf ]] +*/ package siphash /* @@ -6,10 +15,6 @@ package siphash List of contributors: zhibog: Initial implementation. - - Implementation of the SipHash hashing algorithm, as defined at and - - Use the specific procedures for a certain setup. The generic procdedures will default to Siphash 2-4 */ import "core:crypto" diff --git a/core/crypto/sm3/sm3.odin b/core/crypto/sm3/sm3.odin index 2faf37380..f910d735b 100644 --- a/core/crypto/sm3/sm3.odin +++ b/core/crypto/sm3/sm3.odin @@ -2,7 +2,7 @@ package sm3 implements the SM3 hash algorithm. See: -- https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3-02 +- [[ https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3-02 ]] */ package sm3 diff --git a/core/crypto/tuplehash/tuplehash.odin b/core/crypto/tuplehash/tuplehash.odin index ed0a3aa87..e5caaa9c9 100644 --- a/core/crypto/tuplehash/tuplehash.odin +++ b/core/crypto/tuplehash/tuplehash.odin @@ -2,7 +2,7 @@ package tuplehash implements the TupleHash and TupleHashXOF algorithms. See: -- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] */ package tuplehash diff --git a/core/crypto/x25519/x25519.odin b/core/crypto/x25519/x25519.odin index f8a301810..412a767b8 100644 --- a/core/crypto/x25519/x25519.odin +++ b/core/crypto/x25519/x25519.odin @@ -3,7 +3,7 @@ package x25519 implements the X25519 (aka curve25519) Elliptic-Curve Diffie-Hellman key exchange protocol. See: -- https://www.rfc-editor.org/rfc/rfc7748 +- [[ https://www.rfc-editor.org/rfc/rfc7748 ]] */ package x25519 diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin index 8611d7726..ca8bd7817 100644 --- a/core/debug/trace/trace_nil.odin +++ b/core/debug/trace/trace_nil.odin @@ -1,4 +1,6 @@ -//+build !windows !linux !darwin +//+build !windows +//+build !linux +//+build !darwin package debug_trace import "base:runtime" diff --git a/core/dynlib/doc.odin b/core/dynlib/doc.odin index f5c91c54e..487fcb715 100644 --- a/core/dynlib/doc.odin +++ b/core/dynlib/doc.odin @@ -4,7 +4,6 @@ Package `core:dynlib` implements loading of shared libraries/DLLs and their symb The behaviour of dynamically loaded libraries is specific to the target platform of the program. For in depth detail on the underlying behaviour please refer to your target platform's documentation. -See `example` directory for an example library exporting 3 symbols and a host program loading them automatically -by defining a symbol table struct. +For a full example, see: [[ core/dynlib/example; https://github.com/odin-lang/Odin/tree/master/core/dynlib/example ]] */ package dynlib diff --git a/core/encoding/ansi/doc.odin b/core/encoding/ansi/doc.odin index a0945c581..966e6be00 100644 --- a/core/encoding/ansi/doc.odin +++ b/core/encoding/ansi/doc.odin @@ -13,8 +13,8 @@ If your terminal supports 24-bit true color mode, you can also do this: fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) For more information, see: - 1. https://en.wikipedia.org/wiki/ANSI_escape_code - 2. https://www.vt100.net/docs/vt102-ug/chapter5.html - 3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html +- [[ https://en.wikipedia.org/wiki/ANSI_escape_code ]] +- [[ https://www.vt100.net/docs/vt102-ug/chapter5.html ]] +- [[ https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ]] */ package ansi diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 7897b2a37..692be0020 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -3,6 +3,7 @@ package encoding_cbor import "base:intrinsics" import "core:encoding/json" +import "core:encoding/hex" import "core:io" import "core:mem" import "core:strconv" @@ -399,11 +400,11 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i io.write_string(w, str) or_return case bool: io.write_string(w, "true" if v else "false") or_return - case Nil: io.write_string(w, "nil") or_return + case Nil: io.write_string(w, "null") or_return case Undefined: io.write_string(w, "undefined") or_return case ^Bytes: io.write_string(w, "h'") or_return - for b in v { io.write_int(w, int(b), 16) or_return } + hex.encode_into_writer(w, v^) or_return io.write_string(w, "'") or_return case ^Text: io.write_string(w, `"`) or_return diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 6657807f5..aca71deb2 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -481,9 +481,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er } } - marshal_entry :: #force_inline proc(e: Encoder, info: runtime.Type_Info_Struct, v: any, name: string, i: int) -> Marshal_Error { - err_conv(_encode_text(e, name)) or_return - + marshal_entry :: #force_inline proc(e: Encoder, info: runtime.Type_Info_Struct, v: any, i: int) -> Marshal_Error { id := info.types[i].id data := rawptr(uintptr(v.data) + info.offsets[i]) field_any := any{data, id} @@ -517,7 +515,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er if .Deterministic_Map_Sorting in e.flags { Name :: struct { - name: string, + name: []byte, field: int, } entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return @@ -529,16 +527,19 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er continue } - append(&entries, Name{fname, i}) or_return + key_builder := strings.builder_make(e.temp_allocator) or_return + err_conv(_encode_text(Encoder{e.flags, strings.to_stream(&key_builder), e.temp_allocator}, fname)) or_return + append(&entries, Name{key_builder.buf[:], i}) or_return } // Sort lexicographic on the bytes of the key. slice.sort_by_cmp(entries[:], proc(a, b: Name) -> slice.Ordering { - return slice.Ordering(bytes.compare(transmute([]byte)a.name, transmute([]byte)b.name)) + return slice.Ordering(bytes.compare(a.name, b.name)) }) for entry in entries { - marshal_entry(e, info, v, entry.name, entry.field) or_return + io.write_full(e.writer, entry.name) or_return + marshal_entry(e, info, v, entry.field) or_return } } else { for _, i in info.names[:info.field_count] { @@ -547,7 +548,8 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er continue } - marshal_entry(e, info, v, fname, i) or_return + err_conv(_encode_text(e, fname)) or_return + marshal_entry(e, info, v, i) or_return } } return diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin new file mode 100644 index 000000000..bfeadafd6 --- /dev/null +++ b/core/encoding/csv/doc.odin @@ -0,0 +1,96 @@ +/* +package csv reads and writes comma-separated values (CSV) files. +This package supports the format described in [[ RFC 4180; https://tools.ietf.org/html/rfc4180.html ]] + +Example: + package main + + import "core:fmt" + import "core:encoding/csv" + import "core:os" + + // Requires keeping the entire CSV file in memory at once + iterate_csv_from_string :: proc(filename: string) { + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + csv_data, ok := os.read_entire_file(filename) + if ok { + csv.reader_init_with_string(&r, string(csv_data)) + } else { + fmt.printfln("Unable to open file: %v", filename) + return + } + defer delete(csv_data) + + for r, i, err in csv.iterator_next(&r) { + if err != nil { /* Do something with error */ } + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + } + + // Reads the CSV as it's processed (with a small buffer) + iterate_csv_from_stream :: proc(filename: string) { + fmt.printfln("Hellope from %v", filename) + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + handle, err := os.open(filename) + if err != nil { + fmt.eprintfln("Error opening file: %v", filename) + return + } + defer os.close(handle) + csv.reader_init(&r, os.stream_from_handle(handle)) + + for r, i in csv.iterator_next(&r) { + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + fmt.printfln("Error: %v", csv.iterator_last_error(r)) + } + + // Read all records at once + read_csv_from_string :: proc(filename: string) { + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + csv_data, ok := os.read_entire_file(filename) + if ok { + csv.reader_init_with_string(&r, string(csv_data)) + } else { + fmt.printfln("Unable to open file: %v", filename) + return + } + defer delete(csv_data) + + records, err := csv.read_all(&r) + if err != nil { /* Do something with CSV parse error */ } + + defer { + for rec in records { + delete(rec) + } + delete(records) + } + + for r, i in records { + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + } +*/ +package encoding_csv diff --git a/core/encoding/csv/example.odin b/core/encoding/csv/example.odin deleted file mode 100644 index d791eb33b..000000000 --- a/core/encoding/csv/example.odin +++ /dev/null @@ -1,88 +0,0 @@ -//+build ignore -package encoding_csv - -import "core:fmt" -import "core:encoding/csv" -import "core:os" - -// Requires keeping the entire CSV file in memory at once -iterate_csv_from_string :: proc(filename: string) { - r: csv.Reader - r.trim_leading_space = true - r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it - defer csv.reader_destroy(&r) - - if csv_data, ok := os.read_entire_file(filename); ok { - csv.reader_init_with_string(&r, string(csv_data)) - defer delete(csv_data) - } else { - fmt.printfln("Unable to open file: %v", filename) - return - } - - for r, i, err in csv.iterator_next(&r) { - if err != nil { /* Do something with error */ } - for f, j in r { - fmt.printfln("Record %v, field %v: %q", i, j, f) - } - } -} - -// Reads the CSV as it's processed (with a small buffer) -iterate_csv_from_stream :: proc(filename: string) { - fmt.printfln("Hellope from %v", filename) - r: csv.Reader - r.trim_leading_space = true - r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it - defer csv.reader_destroy(&r) - - handle, err := os.open(filename) - if err != nil { - fmt.eprintfln("Error opening file: %v", filename) - return - } - defer os.close(handle) - csv.reader_init(&r, os.stream_from_handle(handle)) - - for r, i in csv.iterator_next(&r) { - for f, j in r { - fmt.printfln("Record %v, field %v: %q", i, j, f) - } - } - fmt.printfln("Error: %v", csv.iterator_last_error(r)) -} - -// Read all records at once -read_csv_from_string :: proc(filename: string) { - r: csv.Reader - r.trim_leading_space = true - r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it - defer csv.reader_destroy(&r) - - if csv_data, ok := os.read_entire_file(filename); ok { - csv.reader_init_with_string(&r, string(csv_data)) - defer delete(csv_data) - } else { - fmt.printfln("Unable to open file: %v", filename) - return - } - - records, err := csv.read_all(&r) - if err != nil { /* Do something with CSV parse error */ } - - defer { - for rec in records { - delete(rec) - } - delete(records) - } - - for r, i in records { - for f, j in r { - fmt.printfln("Record %v, field %v: %q", i, j, f) - } - } -} \ No newline at end of file diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index ebc7b39a0..5348624d5 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -1,5 +1,5 @@ // package csv reads and writes comma-separated values (CSV) files. -// This package supports the format described in RFC 4180 +// This package supports the format described in [[ RFC 4180; https://tools.ietf.org/html/rfc4180.html ]] package encoding_csv import "core:bufio" @@ -484,4 +484,4 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all r.fields_per_record = len(dst) } return dst[:], err -} \ No newline at end of file +} diff --git a/core/encoding/endian/doc.odin b/core/encoding/endian/doc.odin index 8ebefd0a4..0b43e3097 100644 --- a/core/encoding/endian/doc.odin +++ b/core/encoding/endian/doc.odin @@ -2,22 +2,23 @@ Package endian implements a simple translation between bytes and numbers with specific endian encodings. - buf: [100]u8 - put_u16(buf[:], .Little, 16) or_return +Example: + buf: [100]u8 + put_u16(buf[:], .Little, 16) or_return - You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: - put_u16(buf[:], 16) or_return + // You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: + put_u16(buf[:], 16) or_return - The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of - each of their fields, or use a header field which specifies it for the entire file. + // The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of + // each of their fields, or use a header field which specifies it for the entire file. - e.g. a file which specifies it at the top for all fields could do this: - file_order := .Little if buf[0] == 0 else .Big - field := get_u16(buf[1:], file_order) or_return + // e.g. a file which specifies it at the top for all fields could do this: + file_order := .Little if buf[0] == 0 else .Big + field := get_u16(buf[1:], file_order) or_return - If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, - be that your future self or someone else. + // If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, + // be that your future self or someone else. - field := get_u16(buf[:], .Big) or_return + field := get_u16(buf[:], .Big) or_return */ package encoding_endian diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index f5208ad6f..d2f1d46b2 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -1,24 +1,26 @@ -package encoding_unicode_entity /* - A unicode entity encoder/decoder - Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-3 license. - This code has several procedures to map unicode runes to/from different textual encodings. - - SGML/XML/HTML entity - -- &#; - -- &#x; - -- &; (If the lookup tables are compiled in). - Reference: https://www.w3.org/2003/entities/2007xml/unicode.xml - - - URL encode / decode %hex entity - Reference: https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1 - List of contributors: Jeroen van Rijn: Initial implementation. */ +/* + A unicode entity encoder/decoder. + + This code has several procedures to map unicode runes to/from different textual encodings. + - SGML/XML/HTML entity + - &#; + - &#x; + - &; (If the lookup tables are compiled in). + Reference: [[ https://www.w3.org/2003/entities/2007xml/unicode.xml ]] + + - URL encode / decode %hex entity + Reference: [[ https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1 ]] +*/ +package encoding_unicode_entity + import "core:unicode/utf8" import "core:unicode" import "core:strings" @@ -353,4 +355,4 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X } return false, .None -} \ No newline at end of file +} diff --git a/core/encoding/entity/generated.odin b/core/encoding/entity/generated.odin index 0c4742149..52027ae03 100644 --- a/core/encoding/entity/generated.odin +++ b/core/encoding/entity/generated.odin @@ -42,7 +42,7 @@ XML_NAME_TO_RUNE_MAX_LENGTH :: 31 Input: entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. - Output: + Returns: "decoded" - The decoded rune if found by name, or -1 otherwise. "ok" - true if found, false if not. diff --git a/core/encoding/hex/hex.odin b/core/encoding/hex/hex.odin index c2cd89c5b..c1753003e 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -1,5 +1,6 @@ package encoding_hex +import "core:io" import "core:strings" encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check { @@ -14,6 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_locat return dst } +encode_into_writer :: proc(dst: io.Writer, src: []byte) -> io.Error { + for v in src { + io.write(dst, {HEXTABLE[v>>4], HEXTABLE[v&0x0f]}) or_return + } + return nil +} decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check { if len(src) % 2 == 1 { diff --git a/core/encoding/hxa/doc.odin b/core/encoding/hxa/doc.odin index 230d6ea66..b696bef7e 100644 --- a/core/encoding/hxa/doc.odin +++ b/core/encoding/hxa/doc.odin @@ -1,83 +1,89 @@ -// Implementation of the HxA 3D asset format -// HxA is a interchangeable graphics asset format. -// Designed by Eskil Steenberg. @quelsolaar / eskil 'at' obsession 'dot' se / www.quelsolaar.com -// -// Author of this Odin package: Ginger Bill -// -// Following comment is copied from the original C-implementation -// --------- -// -Does the world need another Graphics file format? -// Unfortunately, Yes. All existing formats are either too large and complicated to be implemented from -// scratch, or don't have some basic features needed in modern computer graphics. -// -Who is this format for? -// For people who want a capable open Graphics format that can be implemented from scratch in -// a few hours. It is ideal for graphics researchers, game developers or other people who -// wants to build custom graphics pipelines. Given how easy it is to parse and write, it -// should be easy to write utilities that process assets to preform tasks like: generating -// normals, light-maps, tangent spaces, Error detection, GPU optimization, LOD generation, -// and UV mapping. -// -Why store images in the format when there are so many good image formats already? -// Yes there are, but only for 2D RGB/RGBA images. A lot of computer graphics rendering rely -// on 1D, 3D, cube, multilayer, multi channel, floating point bitmap buffers. There almost no -// formats for this kind of data. Also 3D files that reference separate image files rely on -// file paths, and this often creates issues when the assets are moved. By including the -// texture data in the files directly the assets become self contained. -// -Why doesn't the format support ? -// Because the entire point is to make a format that can be implemented. Features like NURBSs, -// Construction history, or BSP trees would make the format too large to serve its purpose. -// The facilities of the formats to store meta data should make the format flexible enough -// for most uses. Adding HxA support should be something anyone can do in a days work. -// -// Structure: -// ---------- -// HxA is designed to be extremely simple to parse, and is therefore based around conventions. It has -// a few basic structures, and depending on how they are used they mean different things. This means -// that you can implement a tool that loads the entire file, modifies the parts it cares about and -// leaves the rest intact. It is also possible to write a tool that makes all data in the file -// editable without the need to understand its use. It is also possible for anyone to use the format -// to store data axillary data. Anyone who wants to store data not covered by a convention can submit -// a convention to extend the format. There should never be a convention for storing the same data in -// two differed ways. -// The data is story in a number of nodes that are stored in an array. Each node stores an array of -// meta data. Meta data can describe anything you want, and a lot of conventions will use meta data -// to store additional information, for things like transforms, lights, shaders and animation. -// Data for Vertices, Corners, Faces, and Pixels are stored in named layer stacks. Each stack consists -// of a number of named layers. All layers in the stack have the same number of elements. Each layer -// describes one property of the primitive. Each layer can have multiple channels and each layer can -// store data of a different type. -// -// HaX stores 3 kinds of nodes -// - Pixel data. -// - Polygon geometry data. -// - Meta data only. -// -// Pixel Nodes stores pixels in a layer stack. A layer may store things like Albedo, Roughness, -// Reflectance, Light maps, Masks, Normal maps, and Displacement. Layers use the channels of the -// layers to store things like color. The length of the layer stack is determined by the type and -// dimensions stored in the -// -// Geometry data is stored in 3 separate layer stacks for: vertex data, corner data and face data. The -// vertex data stores things like verities, blend shapes, weight maps, and vertex colors. The first -// layer in a vertex stack has to be a 3 channel layer named "position" describing the base position -// of the vertices. The corner stack describes data per corner or edge of the polygons. It can be used -// for things like UV, normals, and adjacency. The first layer in a corner stack has to be a 1 channel -// integer layer named "index" describing the vertices used to form polygons. The last value in each -// polygon has a negative - 1 index to indicate the end of the polygon. -// -// Example: -// A quad and a tri with the vertex index: -// [0, 1, 2, 3] [1, 4, 2] -// is stored: -// [0, 1, 2, -4, 1, 4, -3] -// The face stack stores values per face. the length of the face stack has to match the number of -// negative values in the index layer in the corner stack. The face stack can be used to store things -// like material index. -// -// Storage -// ------- -// All data is stored in little endian byte order with no padding. The layout mirrors the structs -// defined below with a few exceptions. All names are stored as a 8-bit unsigned integer indicating -// the length of the name followed by that many characters. Termination is not stored in the file. -// Text strings stored in meta data are stored the same way as names, but instead of a 8-bit unsigned -// integer a 32-bit unsigned integer is used. -package encoding_hxa \ No newline at end of file +/* +Implementation of the HxA 3D asset format +HxA is a interchangeable graphics asset format. +Designed by Eskil Steenberg. @quelsolaar / eskil 'at' obsession 'dot' se / www.quelsolaar.com + +Author of this Odin package: Ginger Bill + +Following comment is copied from the original C-implementation +--------- +- Does the world need another Graphics file format? +Unfortunately, Yes. All existing formats are either too large and complicated to be implemented from +scratch, or don't have some basic features needed in modern computer graphics. + +- Who is this format for? +For people who want a capable open Graphics format that can be implemented from scratch in +a few hours. It is ideal for graphics researchers, game developers or other people who +wants to build custom graphics pipelines. Given how easy it is to parse and write, it +should be easy to write utilities that process assets to preform tasks like: generating +normals, light-maps, tangent spaces, Error detection, GPU optimization, LOD generation, +and UV mapping. + +- Why store images in the format when there are so many good image formats already? +Yes there are, but only for 2D RGB/RGBA images. A lot of computer graphics rendering rely +on 1D, 3D, cube, multilayer, multi channel, floating point bitmap buffers. There almost no +formats for this kind of data. Also 3D files that reference separate image files rely on +file paths, and this often creates issues when the assets are moved. By including the +texture data in the files directly the assets become self contained. + +- Why doesn't the format support ? +Because the entire point is to make a format that can be implemented. Features like NURBSs, +Construction history, or BSP trees would make the format too large to serve its purpose. +The facilities of the formats to store meta data should make the format flexible enough +for most uses. Adding HxA support should be something anyone can do in a days work. + +Structure: +---------- +HxA is designed to be extremely simple to parse, and is therefore based around conventions. It has +a few basic structures, and depending on how they are used they mean different things. This means +that you can implement a tool that loads the entire file, modifies the parts it cares about and +leaves the rest intact. It is also possible to write a tool that makes all data in the file +editable without the need to understand its use. It is also possible for anyone to use the format +to store data axillary data. Anyone who wants to store data not covered by a convention can submit +a convention to extend the format. There should never be a convention for storing the same data in +two differed ways. + +The data is story in a number of nodes that are stored in an array. Each node stores an array of +meta data. Meta data can describe anything you want, and a lot of conventions will use meta data +to store additional information, for things like transforms, lights, shaders and animation. +Data for Vertices, Corners, Faces, and Pixels are stored in named layer stacks. Each stack consists +of a number of named layers. All layers in the stack have the same number of elements. Each layer +describes one property of the primitive. Each layer can have multiple channels and each layer can +store data of a different type. + +HaX stores 3 kinds of nodes +- Pixel data. +- Polygon geometry data. +- Meta data only. + +Pixel Nodes stores pixels in a layer stack. A layer may store things like Albedo, Roughness, +Reflectance, Light maps, Masks, Normal maps, and Displacement. Layers use the channels of the +layers to store things like color. +The length of the layer stack is determined by the type and dimensions stored in the Geometry data +is stored in 3 separate layer stacks for: vertex data, corner data and face data. The +vertex data stores things like verities, blend shapes, weight maps, and vertex colors. The first +layer in a vertex stack has to be a 3 channel layer named "position" describing the base position +of the vertices. The corner stack describes data per corner or edge of the polygons. It can be used +for things like UV, normals, and adjacency. The first layer in a corner stack has to be a 1 channel +integer layer named "index" describing the vertices used to form polygons. The last value in each +polygon has a negative - 1 index to indicate the end of the polygon. + +For Example: + A quad and a tri with the vertex index: + [0, 1, 2, 3] [1, 4, 2] + is stored: + [0, 1, 2, -4, 1, 4, -3] + +The face stack stores values per face. the length of the face stack has to match the number of +negative values in the index layer in the corner stack. The face stack can be used to store things +like material index. + +Storage: +------- +All data is stored in little endian byte order with no padding. The layout mirrors the structs +defined below with a few exceptions. All names are stored as a 8-bit unsigned integer indicating +the length of the name followed by that many characters. Termination is not stored in the file. +Text strings stored in meta data are stored the same way as names, but instead of a 8-bit unsigned +integer a 32-bit unsigned integer is used. +*/ +package encoding_hxa diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 127bce650..738e20c68 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -116,7 +116,30 @@ assign_int :: proc(val: any, i: $T) -> bool { case int: dst = int (i) case uint: dst = uint (i) case uintptr: dst = uintptr(i) - case: return false + case: + ti := type_info_of(v.id) + if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + do_byte_swap := !reflect.bit_set_is_big_endian(v) + switch ti.size * 8 { + case 0: // no-op. + case 8: + x := (^u8)(v.data) + x^ = u8(i) + case 16: + x := (^u16)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u16(i)) : u16(i) + case 32: + x := (^u32)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u32(i)) : u32(i) + case 64: + x := (^u64)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u64(i)) : u64(i) + case: + panic("unknown bit_size size") + } + return true + } + return false } return true } diff --git a/core/encoding/uuid/doc.odin b/core/encoding/uuid/doc.odin index 6fa375b72..f910c33d8 100644 --- a/core/encoding/uuid/doc.odin +++ b/core/encoding/uuid/doc.odin @@ -21,8 +21,9 @@ cryptographically-secure, per RFC 9562's suggestion. - Version 6 without either a clock or node argument. - Version 7 in all cases. -Here's an example of how to set up one: - +Example: + package main + import "core:crypto" import "core:encoding/uuid" @@ -40,7 +41,7 @@ Here's an example of how to set up one: For more information on the specifications, see here: -- https://www.rfc-editor.org/rfc/rfc4122.html -- https://www.rfc-editor.org/rfc/rfc9562.html +- [[ https://www.rfc-editor.org/rfc/rfc4122.html ]] +- [[ https://www.rfc-editor.org/rfc/rfc9562.html ]] */ package uuid diff --git a/core/encoding/uuid/writing.odin b/core/encoding/uuid/writing.odin index 499cba72b..7acaa3cd7 100644 --- a/core/encoding/uuid/writing.odin +++ b/core/encoding/uuid/writing.odin @@ -11,7 +11,7 @@ Write a UUID in the 8-4-4-4-12 format. This procedure performs error checking with every byte written. If you can guarantee beforehand that your stream has enough space to hold the -UUID (32 bytes), then it is better to use `unsafe_write` instead as that will +UUID (36 bytes), then it is better to use `unsafe_write` instead as that will be faster. Inputs: @@ -22,7 +22,7 @@ Returns: - error: An `io` error, if one occurred, otherwise `nil`. */ write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_check { - write_octet :: proc (w: io.Writer, octet: u8) -> io.Error #no_bounds_check { + write_octet :: proc(w: io.Writer, octet: u8) -> io.Error #no_bounds_check { high_nibble := octet >> 4 low_nibble := octet & 0xF @@ -31,15 +31,15 @@ write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_chec return nil } - for index in 0 ..< 4 { write_octet(w, id[index]) or_return } + for index in 0 ..< 4 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 4 ..< 6 { write_octet(w, id[index]) or_return } + for index in 4 ..< 6 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 6 ..< 8 { write_octet(w, id[index]) or_return } + for index in 6 ..< 8 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 8 ..< 10 { write_octet(w, id[index]) or_return } + for index in 8 ..< 10 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 10 ..< 16 { write_octet(w, id[index]) or_return } + for index in 10 ..< 16 {write_octet(w, id[index]) or_return} return nil } @@ -54,7 +54,7 @@ Inputs: - id: The identifier to convert. */ unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check { - write_octet :: proc (w: io.Writer, octet: u8) #no_bounds_check { + write_octet :: proc(w: io.Writer, octet: u8) #no_bounds_check { high_nibble := octet >> 4 low_nibble := octet & 0xF @@ -62,15 +62,15 @@ unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check { io.write_byte(w, strconv.digits[low_nibble]) } - for index in 0 ..< 4 { write_octet(w, id[index]) } + for index in 0 ..< 4 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 4 ..< 6 { write_octet(w, id[index]) } + for index in 4 ..< 6 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 6 ..< 8 { write_octet(w, id[index]) } + for index in 6 ..< 8 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 8 ..< 10 { write_octet(w, id[index]) } + for index in 8 ..< 10 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 10 ..< 16 { write_octet(w, id[index]) } + for index in 10 ..< 16 {write_octet(w, id[index])} } /* @@ -106,7 +106,7 @@ Convert a UUID to a string in the 8-4-4-4-12 format. Inputs: - id: The identifier to convert. -- buffer: A byte buffer to store the result. Must be at least 32 bytes large. +- buffer: A byte buffer to store the result. Must be at least 36 bytes large. - loc: The caller location for debugging purposes (default: #caller_location) Returns: @@ -119,7 +119,11 @@ to_string_buffer :: proc( ) -> ( str: string, ) { - assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc) + assert( + len(buffer) >= EXPECTED_LENGTH, + "The buffer provided is not at least 36 bytes large.", + loc, + ) builder := strings.builder_from_bytes(buffer) unsafe_write(strings.to_writer(&builder), id) return strings.to_string(builder) @@ -129,3 +133,4 @@ to_string :: proc { to_string_allocated, to_string_buffer, } + diff --git a/core/encoding/varint/doc.odin b/core/encoding/varint/doc.odin index c0a09873c..a00cfed15 100644 --- a/core/encoding/varint/doc.odin +++ b/core/encoding/varint/doc.odin @@ -1,10 +1,11 @@ /* - Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others. +Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others. - Author of this Odin package: Jeroen van Rijn +Author of this Odin package: Jeroen van Rijn + +Example: + package main - Example: - ```odin import "core:encoding/varint" import "core:fmt" @@ -22,7 +23,5 @@ assert(decoded_val == value && decode_size == encode_size && decode_err == .None) fmt.printf("Decoded as %v, using %v byte%v\n", decoded_val, decode_size, "" if decode_size == 1 else "s") } - ``` - */ -package encoding_varint \ No newline at end of file +package encoding_varint diff --git a/core/encoding/varint/leb128.odin b/core/encoding/varint/leb128.odin index ca6513f04..606c57ba7 100644 --- a/core/encoding/varint/leb128.odin +++ b/core/encoding/varint/leb128.odin @@ -6,8 +6,6 @@ Jeroen van Rijn: Initial implementation. */ -// package varint implements variable length integer encoding and decoding using -// the LEB128 format as used by DWARF debug info, Android .dex and other file formats. package encoding_varint // In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file. @@ -160,4 +158,4 @@ encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) { buf[size - 1] = u8(low) } return -} \ No newline at end of file +} diff --git a/core/encoding/xml/doc.odin b/core/encoding/xml/doc.odin new file mode 100644 index 000000000..10d9f78be --- /dev/null +++ b/core/encoding/xml/doc.odin @@ -0,0 +1,23 @@ +/* +XML 1.0 / 1.1 parser + +A from-scratch XML implementation, loosely modelled on the [[ spec; https://www.w3.org/TR/2006/REC-xml11-20060816 ]]. + +Features: +- Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage. +- Simple to understand and use. Small. + +Caveats: +- We do NOT support HTML in this package, as that may or may not be valid XML. + If it works, great. If it doesn't, that's not considered a bug. + +- We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences. +- <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options. + +MAYBE: +- XML writer? +- Serialize/deserialize Odin types? + +For a full example, see: [[ core/encoding/xml/example; https://github.com/odin-lang/Odin/tree/master/core/encoding/xml/example ]] +*/ +package encoding_xml diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index b9656900f..b8c8b13a4 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -1,29 +1,11 @@ /* - XML 1.0 / 1.1 parser + 2021-2022 Jeroen van Rijn . + available under Odin's BSD-3 license. - 2021-2022 Jeroen van Rijn . - available under Odin's BSD-3 license. - - from-scratch XML implementation, loosely modelled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816). - -Features: -- Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage. -- Simple to understand and use. Small. - -Caveats: -- We do NOT support HTML in this package, as that may or may not be valid XML. - If it works, great. If it doesn't, that's not considered a bug. - -- We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences. -- <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options. - -MAYBE: -- XML writer? -- Serialize/deserialize Odin types? - -List of contributors: -- Jeroen van Rijn: Initial implementation. + List of contributors: + - Jeroen van Rijn: Initial implementation. */ + package encoding_xml // An XML 1.0 / 1.1 parser diff --git a/core/flags/constants.odin b/core/flags/constants.odin index ab3dc9a0a..6f5281928 100644 --- a/core/flags/constants.odin +++ b/core/flags/constants.odin @@ -12,7 +12,7 @@ IMPORTING_TIME :: #config(ODIN_CORE_FLAGS_USE_TIME, time.IS_SUPPORTED) // Override support for parsing `net` types. // TODO: Update this when the BSDs are supported. -IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin) +IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD) TAG_ARGS :: "args" SUBTAG_NAME :: "name" diff --git a/core/flags/doc.odin b/core/flags/doc.odin index c3663c419..b97547806 100644 --- a/core/flags/doc.odin +++ b/core/flags/doc.odin @@ -11,15 +11,13 @@ Command-Line Syntax: Arguments are treated differently depending on how they're formatted. The format is similar to the Odin binary's way of handling compiler flags. -``` -type handling ------------- ------------------------ - depends on struct layout -- set a bool true -- set flag to option -- set flag to option, alternative syntax --:= set map[key] to value -``` + type handling + ------------ ------------------------ + depends on struct layout + - set a bool true + - set flag to option + - set flag to option, alternative syntax + -:= set map[key] to value Struct Tags: @@ -40,11 +38,9 @@ Under the `args` tag, there are the following subtags: - `indistinct`: allow the setting of distinct types by their base type. `required` may be given a range specifier in the following formats: -``` -min - (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. + if set, valid_index := bit_array.get(&parser.filled_pos, parser.filled_pos.length - 1); set || !valid_index { + // The index below the last one is either set or invalid, which means we're out of space. // Add one free bit by setting the index above to false. - bit_array.set(&parser.filled_pos, 1 + parser.filled_pos.max_index, false) + bit_array.set(&parser.filled_pos, parser.filled_pos.length, false) } pos: int = --- diff --git a/core/flags/internal_rtti_nonbsd.odin b/core/flags/internal_rtti_nonbsd.odin index 196c27ab8..0044898d5 100644 --- a/core/flags/internal_rtti_nonbsd.odin +++ b/core/flags/internal_rtti_nonbsd.odin @@ -1,5 +1,6 @@ //+private -//+build !freebsd !netbsd !openbsd +//+build !netbsd +//+build !openbsd package flags import "core:net" diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 7b86fd1b7..0d11ad8a9 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2728,7 +2728,8 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { } case runtime.Type_Info_Struct, - runtime.Type_Info_Union: + runtime.Type_Info_Union, + runtime.Type_Info_Bit_Field: if ptr == nil { io.write_string(fi.writer, "", &fi.n) return diff --git a/core/hash/xxhash/common.odin b/core/hash/xxhash/common.odin index bbeb60db3..adfc1bac2 100644 --- a/core/hash/xxhash/common.odin +++ b/core/hash/xxhash/common.odin @@ -1,5 +1,4 @@ /* - An implementation of Yann Collet's [xxhash Fast Hash Algorithm](https://cyan4973.github.io/xxHash/). Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-3 license, based on the original C code. @@ -7,6 +6,8 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + +// An implementation of Yann Collet's [[ xxhash Fast Hash Algorithm; https://cyan4973.github.io/xxHash/ ]]. package xxhash import "base:intrinsics" diff --git a/core/hash/xxhash/streaming.odin b/core/hash/xxhash/streaming.odin index f68862f67..f77edf3e9 100644 --- a/core/hash/xxhash/streaming.odin +++ b/core/hash/xxhash/streaming.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "core:mem" @@ -371,4 +372,4 @@ XXH3_generate_secret :: proc(secret_buffer: []u8, custom_seed: []u8) { mem_copy(&secret_buffer[segment_start], &segment, size_of(segment)) } } -} \ No newline at end of file +} diff --git a/core/hash/xxhash/xxhash_3.odin b/core/hash/xxhash/xxhash_3.odin index 9e159260b..293e98528 100644 --- a/core/hash/xxhash/xxhash_3.odin +++ b/core/hash/xxhash/xxhash_3.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "base:intrinsics" diff --git a/core/hash/xxhash/xxhash_32.odin b/core/hash/xxhash/xxhash_32.odin index 3ea1c3cf2..28cd4e177 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "base:intrinsics" diff --git a/core/hash/xxhash/xxhash_64.odin b/core/hash/xxhash/xxhash_64.odin index 3b24f20a1..87e8916d6 100644 --- a/core/hash/xxhash/xxhash_64.odin +++ b/core/hash/xxhash/xxhash_64.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "base:intrinsics" @@ -19,15 +20,15 @@ xxh_u64 :: u64 XXH64_DEFAULT_SEED :: XXH64_hash(0) XXH64_state :: struct { - total_len: XXH64_hash, /*!< Total length hashed. This is always 64-bit. */ - v1: XXH64_hash, /*!< First accumulator lane */ - v2: XXH64_hash, /*!< Second accumulator lane */ - v3: XXH64_hash, /*!< Third accumulator lane */ - v4: XXH64_hash, /*!< Fourth accumulator lane */ - mem64: [4]XXH64_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */ - memsize: XXH32_hash, /*!< Amount of data in @ref mem64 */ - reserved32: XXH32_hash, /*!< Reserved field, needed for padding anyways*/ - reserved64: XXH64_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ + total_len: XXH64_hash, /*!< Total length hashed. This is always 64-bit. */ + v1: XXH64_hash, /*!< First accumulator lane */ + v2: XXH64_hash, /*!< Second accumulator lane */ + v3: XXH64_hash, /*!< Third accumulator lane */ + v4: XXH64_hash, /*!< Fourth accumulator lane */ + mem64: [4]XXH64_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */ + memsize: XXH32_hash, /*!< Amount of data in @ref mem64 */ + reserved32: XXH32_hash, /*!< Reserved field, needed for padding anyways*/ + reserved64: XXH64_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ } XXH64_canonical :: struct { diff --git a/core/image/common.odin b/core/image/common.odin index 6ae9850da..690ebc045 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -590,7 +590,7 @@ Channel :: enum u8 { // 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 { +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 } @@ -1293,7 +1293,7 @@ blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where return T(c & (MAX - 1)) } -blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1 && N <= 4 { +blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1, N <= 4 { MAX :: 256 when T == u8 else 65536 when N == 1 { @@ -1393,6 +1393,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo for p in inp { out[0].rgb = p.r // Gray component. out[0].a = p.g // Alpha component. + out = out[1:] } case: @@ -1417,6 +1418,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo for p in inp { out[0].rgb = p.r // Gray component. out[0].a = p.g // Alpha component. + out = out[1:] } case: diff --git a/core/image/png/doc.odin b/core/image/png/doc.odin new file mode 100644 index 000000000..623c13077 --- /dev/null +++ b/core/image/png/doc.odin @@ -0,0 +1,348 @@ +/* +package png implements a PNG image reader + +The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]]. + +Example: + package main + + import "core:image" + // import "core:image/png" + import "core:bytes" + import "core:fmt" + + // For PPM writer + import "core:mem" + import "core:os" + + main :: proc() { + track := mem.Tracking_Allocator{} + mem.tracking_allocator_init(&track, context.allocator) + + context.allocator = mem.tracking_allocator(&track) + + demo() + + if len(track.allocation_map) > 0 { + fmt.println("Leaks:") + for _, v in track.allocation_map { + fmt.printf("\t%v\n\n", v) + } + } + } + + demo :: proc() { + file: string + + options := image.Options{.return_metadata} + err: image.Error + img: ^image.Image + + file = "../../../misc/logo-slim.png" + + img, err = load(file, options) + defer destroy(img) + + if err != nil { + fmt.printf("Trying to read PNG file %v returned %v\n", file, err) + } else { + fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth) + + if v, ok := img.metadata.(^image.PNG_Info); ok { + // Handle ancillary chunks as you wish. + // We provide helper functions for a few types. + for c in v.chunks { + #partial switch c.header.type { + case .tIME: + if t, t_ok := core_time(c); t_ok { + fmt.printf("[tIME]: %v\n", t) + } + case .gAMA: + if gama, gama_ok := gamma(c); gama_ok { + fmt.printf("[gAMA]: %v\n", gama) + } + case .pHYs: + if phys, phys_ok := phys(c); phys_ok { + if phys.unit == .Meter { + xm := f32(img.width) / f32(phys.ppu_x) + ym := f32(img.height) / f32(phys.ppu_y) + dpi_x, dpi_y := phys_to_dpi(phys) + fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y) + fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y) + fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym) + } else { + fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y) + } + } + case .iTXt, .zTXt, .tEXt: + res, ok_text := text(c) + if ok_text { + if c.header.type == .iTXt { + fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text) + } else { + fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text) + } + } + defer text_destroy(res) + case .bKGD: + fmt.printf("[bKGD] %v\n", img.background) + case .eXIf: + if res, ok_exif := exif(c); ok_exif { + /* + Other than checking the signature and byte order, we don't handle Exif data. + If you wish to interpret it, pass it to an Exif parser. + */ + fmt.printf("[eXIf] %v\n", res) + } + case .PLTE: + if plte, plte_ok := plte(c); plte_ok { + fmt.printf("[PLTE] %v\n", plte) + } else { + fmt.printf("[PLTE] Error\n") + } + case .hIST: + if res, ok_hist := hist(c); ok_hist { + fmt.printf("[hIST] %v\n", res) + } + case .cHRM: + if res, ok_chrm := chrm(c); ok_chrm { + fmt.printf("[cHRM] %v\n", res) + } + case .sPLT: + res, ok_splt := splt(c) + if ok_splt { + fmt.printf("[sPLT] %v\n", res) + } + splt_destroy(res) + case .sBIT: + if res, ok_sbit := sbit(c); ok_sbit { + fmt.printf("[sBIT] %v\n", res) + } + case .iCCP: + res, ok_iccp := iccp(c) + if ok_iccp { + fmt.printf("[iCCP] %v\n", res) + } + iccp_destroy(res) + case .sRGB: + if res, ok_srgb := srgb(c); ok_srgb { + fmt.printf("[sRGB] Rendering intent: %v\n", res) + } + case: + type := c.header.type + name := chunk_type_to_name(&type) + fmt.printf("[%v]: %v\n", name, c.data) + } + } + } + } + + fmt.printf("Done parsing metadata.\n") + + if err == nil && .do_not_decompress_image not_in options && .info not_in options { + if ok := write_image_as_ppm("out.ppm", img); ok { + fmt.println("Saved decoded image.") + } else { + fmt.println("Error saving out.ppm.") + fmt.println(img) + } + } + } + + // Crappy PPM writer used during testing. Don't use in production. + write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) { + + _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) { + if v, ok := bg.?; ok { + res = v + } else { + if high { + l := u16(30 * 256 + 30) + + if (x & 4 == 0) ~ (y & 4 == 0) { + res = [3]u16{l, 0, l} + } else { + res = [3]u16{l >> 1, 0, l >> 1} + } + } else { + if (x & 4 == 0) ~ (y & 4 == 0) { + res = [3]u16{30, 30, 30} + } else { + res = [3]u16{15, 15, 15} + } + } + } + return + } + + // profiler.timed_proc(); + using image + using os + + flags: int = O_WRONLY|O_CREATE|O_TRUNC + + img := image + + // PBM 16-bit images are big endian + when ODIN_ENDIAN == .Little { + if img.depth == 16 { + // The pixel components are in Big Endian. Let's byteswap back. + input := mem.slice_data_cast([]u16, img.pixels.buf[:]) + output := mem.slice_data_cast([]u16be, img.pixels.buf[:]) + #no_bounds_check for v, i in input { + output[i] = u16be(v) + } + } + } + + pix := bytes.buffer_to_bytes(&img.pixels) + + if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) { + return false + } + + mode: int = 0 + when ODIN_OS == .Linux || ODIN_OS == .Darwin { + // NOTE(justasd): 644 (owner read, write; group read; others read) + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH + } + + fd, err := open(filename, flags, mode) + if err != nil { + return false + } + defer close(fd) + + write_string(fd, + fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)), + ) + + if channels == 3 { + // We don't handle transparency here... + write_ptr(fd, raw_data(pix), len(pix)) + } else { + bpp := depth == 16 ? 2 : 1 + bytes_needed := width * height * 3 * bpp + + op := bytes.Buffer{} + bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed) + defer bytes.buffer_destroy(&op) + + if channels == 1 { + if depth == 16 { + assert(len(pix) == width * height * 2) + p16 := mem.slice_data_cast([]u16, pix) + o16 := mem.slice_data_cast([]u16, op.buf[:]) + #no_bounds_check for len(p16) != 0 { + r := u16(p16[0]) + o16[0] = r + o16[1] = r + o16[2] = r + p16 = p16[1:] + o16 = o16[3:] + } + } else { + o := 0 + for i := 0; i < len(pix); i += 1 { + r := pix[i] + op.buf[o ] = r + op.buf[o+1] = r + op.buf[o+2] = r + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else if channels == 2 { + if depth == 16 { + p16 := mem.slice_data_cast([]u16, pix) + o16 := mem.slice_data_cast([]u16, op.buf[:]) + + bgcol := img.background + + #no_bounds_check for len(p16) != 0 { + r := f64(u16(p16[0])) + bg: f64 + if bgcol != nil { + v := bgcol.([3]u16)[0] + bg = f64(v) + } + a := f64(u16(p16[1])) / 65535.0 + l := (a * r) + (1 - a) * bg + + o16[0] = u16(l) + o16[1] = u16(l) + o16[2] = u16(l) + + p16 = p16[2:] + o16 = o16[3:] + } + } else { + o := 0 + for i := 0; i < len(pix); i += 2 { + r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0 + c := u8(f32(r) * a1) + op.buf[o ] = c + op.buf[o+1] = c + op.buf[o+2] = c + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else if channels == 4 { + if depth == 16 { + p16 := mem.slice_data_cast([]u16be, pix) + o16 := mem.slice_data_cast([]u16be, op.buf[:]) + + #no_bounds_check for len(p16) != 0 { + + bg := _bg(img.background, 0, 0) + r := f32(p16[0]) + g := f32(p16[1]) + b := f32(p16[2]) + a := f32(p16[3]) / 65535.0 + + lr := (a * r) + (1 - a) * f32(bg[0]) + lg := (a * g) + (1 - a) * f32(bg[1]) + lb := (a * b) + (1 - a) * f32(bg[2]) + + o16[0] = u16be(lr) + o16[1] = u16be(lg) + o16[2] = u16be(lb) + + p16 = p16[4:] + o16 = o16[3:] + } + } else { + o := 0 + + for i := 0; i < len(pix); i += 4 { + + x := (i / 4) % width + y := i / width / 4 + + _b := _bg(img.background, x, y, false) + bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])} + + r := f32(pix[i]) + g := f32(pix[i+1]) + b := f32(pix[i+2]) + a := f32(pix[i+3]) / 255.0 + + lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0])) + lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1])) + lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2])) + op.buf[o ] = lr + op.buf[o+1] = lg + op.buf[o+2] = lb + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else { + return false + } + } + return true + } +*/ +package png diff --git a/core/image/png/example.odin b/core/image/png/example.odin deleted file mode 100644 index ce491978b..000000000 --- a/core/image/png/example.odin +++ /dev/null @@ -1,351 +0,0 @@ -/* - Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - Ginger Bill: Cosmetic changes. - - An example of how to use `load`. -*/ -//+build ignore -package png - -import "core:image" -// import "core:image/png" -import "core:bytes" -import "core:fmt" - -// For PPM writer -import "core:mem" -import "core:os" - -main :: proc() { - track := mem.Tracking_Allocator{} - mem.tracking_allocator_init(&track, context.allocator) - - context.allocator = mem.tracking_allocator(&track) - - demo() - - if len(track.allocation_map) > 0 { - fmt.println("Leaks:") - for _, v in track.allocation_map { - fmt.printf("\t%v\n\n", v) - } - } -} - -demo :: proc() { - file: string - - options := image.Options{.return_metadata} - err: image.Error - img: ^image.Image - - file = "../../../misc/logo-slim.png" - - img, err = load(file, options) - defer destroy(img) - - if err != nil { - fmt.printf("Trying to read PNG file %v returned %v\n", file, err) - } else { - fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth) - - if v, ok := img.metadata.(^image.PNG_Info); ok { - // Handle ancillary chunks as you wish. - // We provide helper functions for a few types. - for c in v.chunks { - #partial switch c.header.type { - case .tIME: - if t, t_ok := core_time(c); t_ok { - fmt.printf("[tIME]: %v\n", t) - } - case .gAMA: - if gama, gama_ok := gamma(c); gama_ok { - fmt.printf("[gAMA]: %v\n", gama) - } - case .pHYs: - if phys, phys_ok := phys(c); phys_ok { - if phys.unit == .Meter { - xm := f32(img.width) / f32(phys.ppu_x) - ym := f32(img.height) / f32(phys.ppu_y) - dpi_x, dpi_y := phys_to_dpi(phys) - fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y) - fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y) - fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym) - } else { - fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y) - } - } - case .iTXt, .zTXt, .tEXt: - res, ok_text := text(c) - if ok_text { - if c.header.type == .iTXt { - fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text) - } else { - fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text) - } - } - defer text_destroy(res) - case .bKGD: - fmt.printf("[bKGD] %v\n", img.background) - case .eXIf: - if res, ok_exif := exif(c); ok_exif { - /* - Other than checking the signature and byte order, we don't handle Exif data. - If you wish to interpret it, pass it to an Exif parser. - */ - fmt.printf("[eXIf] %v\n", res) - } - case .PLTE: - if plte, plte_ok := plte(c); plte_ok { - fmt.printf("[PLTE] %v\n", plte) - } else { - fmt.printf("[PLTE] Error\n") - } - case .hIST: - if res, ok_hist := hist(c); ok_hist { - fmt.printf("[hIST] %v\n", res) - } - case .cHRM: - if res, ok_chrm := chrm(c); ok_chrm { - fmt.printf("[cHRM] %v\n", res) - } - case .sPLT: - res, ok_splt := splt(c) - if ok_splt { - fmt.printf("[sPLT] %v\n", res) - } - splt_destroy(res) - case .sBIT: - if res, ok_sbit := sbit(c); ok_sbit { - fmt.printf("[sBIT] %v\n", res) - } - case .iCCP: - res, ok_iccp := iccp(c) - if ok_iccp { - fmt.printf("[iCCP] %v\n", res) - } - iccp_destroy(res) - case .sRGB: - if res, ok_srgb := srgb(c); ok_srgb { - fmt.printf("[sRGB] Rendering intent: %v\n", res) - } - case: - type := c.header.type - name := chunk_type_to_name(&type) - fmt.printf("[%v]: %v\n", name, c.data) - } - } - } - } - - fmt.printf("Done parsing metadata.\n") - - if err == nil && .do_not_decompress_image not_in options && .info not_in options { - if ok := write_image_as_ppm("out.ppm", img); ok { - fmt.println("Saved decoded image.") - } else { - fmt.println("Error saving out.ppm.") - fmt.println(img) - } - } -} - -// Crappy PPM writer used during testing. Don't use in production. -write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) { - - _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) { - if v, ok := bg.?; ok { - res = v - } else { - if high { - l := u16(30 * 256 + 30) - - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{l, 0, l} - } else { - res = [3]u16{l >> 1, 0, l >> 1} - } - } else { - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{30, 30, 30} - } else { - res = [3]u16{15, 15, 15} - } - } - } - return - } - - // profiler.timed_proc(); - using image - using os - - flags: int = O_WRONLY|O_CREATE|O_TRUNC - - img := image - - // PBM 16-bit images are big endian - when ODIN_ENDIAN == .Little { - if img.depth == 16 { - // The pixel components are in Big Endian. Let's byteswap back. - input := mem.slice_data_cast([]u16, img.pixels.buf[:]) - output := mem.slice_data_cast([]u16be, img.pixels.buf[:]) - #no_bounds_check for v, i in input { - output[i] = u16be(v) - } - } - } - - pix := bytes.buffer_to_bytes(&img.pixels) - - if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) { - return false - } - - mode: int = 0 - when ODIN_OS == .Linux || ODIN_OS == .Darwin { - // NOTE(justasd): 644 (owner read, write; group read; others read) - mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH - } - - fd, err := open(filename, flags, mode) - if err != nil { - return false - } - defer close(fd) - - write_string(fd, - fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)), - ) - - if channels == 3 { - // We don't handle transparency here... - write_ptr(fd, raw_data(pix), len(pix)) - } else { - bpp := depth == 16 ? 2 : 1 - bytes_needed := width * height * 3 * bpp - - op := bytes.Buffer{} - bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed) - defer bytes.buffer_destroy(&op) - - if channels == 1 { - if depth == 16 { - assert(len(pix) == width * height * 2) - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - #no_bounds_check for len(p16) != 0 { - r := u16(p16[0]) - o16[0] = r - o16[1] = r - o16[2] = r - p16 = p16[1:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 1 { - r := pix[i] - op.buf[o ] = r - op.buf[o+1] = r - op.buf[o+2] = r - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 2 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - - bgcol := img.background - - #no_bounds_check for len(p16) != 0 { - r := f64(u16(p16[0])) - bg: f64 - if bgcol != nil { - v := bgcol.([3]u16)[0] - bg = f64(v) - } - a := f64(u16(p16[1])) / 65535.0 - l := (a * r) + (1 - a) * bg - - o16[0] = u16(l) - o16[1] = u16(l) - o16[2] = u16(l) - - p16 = p16[2:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 2 { - r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0 - c := u8(f32(r) * a1) - op.buf[o ] = c - op.buf[o+1] = c - op.buf[o+2] = c - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 4 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16be, pix) - o16 := mem.slice_data_cast([]u16be, op.buf[:]) - - #no_bounds_check for len(p16) != 0 { - - bg := _bg(img.background, 0, 0) - r := f32(p16[0]) - g := f32(p16[1]) - b := f32(p16[2]) - a := f32(p16[3]) / 65535.0 - - lr := (a * r) + (1 - a) * f32(bg[0]) - lg := (a * g) + (1 - a) * f32(bg[1]) - lb := (a * b) + (1 - a) * f32(bg[2]) - - o16[0] = u16be(lr) - o16[1] = u16be(lg) - o16[2] = u16be(lb) - - p16 = p16[4:] - o16 = o16[3:] - } - } else { - o := 0 - - for i := 0; i < len(pix); i += 4 { - - x := (i / 4) % width - y := i / width / 4 - - _b := _bg(img.background, x, y, false) - bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])} - - r := f32(pix[i]) - g := f32(pix[i+1]) - b := f32(pix[i+2]) - a := f32(pix[i+3]) / 255.0 - - lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0])) - lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1])) - lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2])) - op.buf[o ] = lr - op.buf[o+1] = lg - op.buf[o+2] = lb - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else { - return false - } - } - return true -} diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index 7b6367694..f094b54a9 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -8,6 +8,7 @@ These are a few useful utility functions to work with PNG images. */ + package png import "core:image" diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 177269722..02aef1087 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -8,9 +8,6 @@ */ -// package png implements a PNG image reader -// -// The PNG specification is at https://www.w3.org/TR/PNG/. //+vet !using-stmt package png @@ -1619,4 +1616,4 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH @(init, private) _register :: proc() { image.register(.PNG, load_from_bytes, destroy) -} \ No newline at end of file +} diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index 5cf252fcc..6b6149e60 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -9,7 +9,7 @@ // package qoi implements a QOI image reader // -// The QOI specification is at https://qoiformat.org. +// The QOI specification is at [[ https://qoiformat.org ]]. package qoi import "core:image" diff --git a/core/io/util.odin b/core/io/util.odin index c24eb99c5..e65a69fb3 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -340,6 +340,9 @@ _limited_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, l := (^Limited_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if l.n <= 0 { return 0, .EOF } @@ -376,11 +379,12 @@ Section_Reader :: struct { limit: i64, } -section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) { +section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) -> Reader { s.r = r + s.base = off s.off = off s.limit = off + n - return + return section_reader_to_stream(s) } section_reader_to_stream :: proc(s: ^Section_Reader) -> (out: Stream) { out.data = s @@ -393,6 +397,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s := (^Section_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if s.off >= s.limit { return 0, .EOF } @@ -404,6 +411,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s.off += i64(n) return case .Read_At: + if len(p) == 0 { + return 0, nil + } p, off := p, offset if off < 0 || off >= s.limit - s.base { diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index fb968ccb6..f05f7a258 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,4 +1,5 @@ //+build !freestanding +//+build !orca package log import "core:encoding/ansi" diff --git a/core/log/log.odin b/core/log/log.odin index 0d89fdb74..cbb2e922b 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -67,9 +67,7 @@ Logger :: struct { */ Logger :: runtime.Logger -nil_logger_proc :: proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { - // Do nothing -} +nil_logger_proc :: runtime.default_logger_proc nil_logger :: proc() -> Logger { return Logger{nil_logger_proc, nil, Level.Debug, nil} @@ -157,24 +155,26 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) { logger := context.logger - if logger.procedure == nil { + if logger.procedure == nil || logger.procedure == nil_logger_proc { return } if level < logger.lowest_level { return } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() str := fmt.tprint(..args, sep=sep) //NOTE(Hoej): While tprint isn't thread-safe, no logging is. logger.procedure(logger.data, level, str, logger.options, location) } logf :: proc(level: Level, fmt_str: string, args: ..any, location := #caller_location) { logger := context.logger - if logger.procedure == nil { + if logger.procedure == nil || logger.procedure == nil_logger_proc { return } if level < logger.lowest_level { return } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() str := fmt.tprintf(fmt_str, ..args) logger.procedure(logger.data, level, str, logger.options, location) } diff --git a/core/math/big/common.odin b/core/math/big/common.odin index fabf39520..5428b00ee 100644 --- a/core/math/big/common.odin +++ b/core/math/big/common.odin @@ -195,7 +195,7 @@ Error_String :: #sparse[Error]string{ } Primality_Flag :: enum u8 { - Blum_Blum_Shub = 0, // Make prime congruent to 3 mod 4 + Blum_Blum_Shub = 0, // Make prime congruent to 3 mod 4 Safe = 1, // Make sure (p-1)/2 is prime as well (implies .Blum_Blum_Shub) Second_MSB_On = 3, // Make the 2nd highest bit one } diff --git a/core/math/math.odin b/core/math/math.odin index 3d0ab3c4e..f5e904da6 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -405,6 +405,12 @@ remap :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T) return ((old_value - old_min) / old_range) * new_range + new_min } +@(require_results) +remap_clamped :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T) -> (x: T) where intrinsics.type_is_numeric(T), !intrinsics.type_is_array(T) { + remapped := #force_inline remap(old_value, old_min, old_max, new_min, new_max) + return clamp(remapped, new_min, new_max) +} + @(require_results) wrap :: proc "contextless" (x, y: $T) -> T where intrinsics.type_is_numeric(T), !intrinsics.type_is_array(T) { tmp := mod(x, y) @@ -2443,6 +2449,36 @@ hypot :: proc{ hypot_f64, hypot_f64le, hypot_f64be, } +@(require_results) +count_digits_of_base :: proc "contextless" (value: $T, $base: int) -> (digits: int) where intrinsics.type_is_integer(T) { + #assert(base >= 2, "base must be 2 or greater.") + + value := value + when !intrinsics.type_is_unsigned(T) { + value = abs(value) + } + + when base == 2 { + digits = max(1, 8 * size_of(T) - int(intrinsics.count_leading_zeros(value))) + } else when intrinsics.count_ones(base) == 1 { + free_bits := 8 * size_of(T) - int(intrinsics.count_leading_zeros(value)) + digits, free_bits = divmod(free_bits, intrinsics.constant_log2(base)) + if free_bits > 0 { + digits += 1 + } + digits = max(1, digits) + } else { + digits = 1 + base := cast(T)base + for value >= base { + value /= base + digits += 1 + } + } + + return +} + F16_DIG :: 3 F16_EPSILON :: 0.00097656 F16_GUARD :: 0 diff --git a/core/math/noise/opensimplex2.odin b/core/math/noise/opensimplex2.odin index d28356f2c..634c32948 100644 --- a/core/math/noise/opensimplex2.odin +++ b/core/math/noise/opensimplex2.odin @@ -1,8 +1,8 @@ /* OpenSimplex2 noise implementation. - Ported from https://github.com/KdotJPG/OpenSimplex2. - Copyright 2022 Yuki2 (https://github.com/NoahR02) + Ported from [[ https://github.com/KdotJPG/OpenSimplex2 }]. + Copyright 2022 Yuki2 [[ https://github.com/NoahR02 ]] */ package math_noise @@ -177,4 +177,4 @@ noise_4d_fallback :: proc(seed: i64, coord: Vec4) -> (value: f32) { // Get points for A4 lattice skew := f64(SKEW_4D) * (coord.x + coord.y + coord.z + coord.w) return _internal_noise_4d_unskewed_base(seed, coord + skew) -} \ No newline at end of file +} diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index acd77241f..e51d971e1 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -178,11 +178,11 @@ make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocato } @(require_results) make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { - return runtime.make_dynamic_array(T, len, allocator, loc) + return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) } @(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) { - return runtime.make_dynamic_array(T, len, cap, allocator, loc) + return runtime.make_dynamic_array_len_cap(T, len, cap, allocator, loc) } @(require_results) make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = 1< (m: T, err: Allocator_Error) { diff --git a/core/mem/doc.odin b/core/mem/doc.odin index e232428c2..44c93f798 100644 --- a/core/mem/doc.odin +++ b/core/mem/doc.odin @@ -5,31 +5,30 @@ package mem implements various types of allocators. An example of how to use the `Tracking_Allocator` to track subsequent allocations in your program and report leaks and bad frees: -```odin -package foo +Example: + package foo -import "core:mem" -import "core:fmt" + import "core:mem" + import "core:fmt" -_main :: proc() { - do stuff -} - -main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) - - _main() - - for _, leak in track.allocation_map { - fmt.printf("%v leaked %m\n", leak.location, leak.size) + _main :: proc() { + // do stuff } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + + main :: proc() { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + defer mem.tracking_allocator_destroy(&track) + context.allocator = mem.tracking_allocator(&track) + + _main() + + for _, leak in track.allocation_map { + fmt.printf("%v leaked %m\n", leak.location, leak.size) + } + for bad_free in track.bad_free_array { + fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } } -} -``` */ package mem diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 80c231c31..79407d80d 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -49,6 +49,10 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING arena.curr_block = memory_block_alloc(0, reserved, {}) or_return arena.total_used = 0 arena.total_reserved = arena.curr_block.reserved + + if arena.minimum_block_size == 0 { + arena.minimum_block_size = reserved + } return } diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin index 4e53aba66..4afc33813 100644 --- a/core/mem/virtual/virtual.odin +++ b/core/mem/virtual/virtual.odin @@ -7,6 +7,11 @@ _ :: runtime DEFAULT_PAGE_SIZE := uint(4096) +@(init, private) +platform_memory_init :: proc() { + _platform_memory_init() +} + Allocator_Error :: mem.Allocator_Error @(require_results) diff --git a/core/mem/virtual/virtual_darwin.odin b/core/mem/virtual/virtual_darwin.odin deleted file mode 100644 index d2e3c8b51..000000000 --- a/core/mem/virtual/virtual_darwin.odin +++ /dev/null @@ -1,165 +0,0 @@ -//+build darwin -//+private -package mem_virtual - -foreign import libc "system:System.framework" -import "core:c" - -PROT_NONE :: 0x0 /* [MC2] no permissions */ -PROT_READ :: 0x1 /* [MC2] pages can be read */ -PROT_WRITE :: 0x2 /* [MC2] pages can be written */ -PROT_EXEC :: 0x4 /* [MC2] pages can be executed */ - -// Sharing options -MAP_SHARED :: 0x1 /* [MF|SHM] share changes */ -MAP_PRIVATE :: 0x2 /* [MF|SHM] changes are private */ - -// Other flags -MAP_FIXED :: 0x0010 /* [MF|SHM] interpret addr exactly */ -MAP_RENAME :: 0x0020 /* Sun: rename private pages to file */ -MAP_NORESERVE :: 0x0040 /* Sun: don't reserve needed swap area */ -MAP_RESERVED0080 :: 0x0080 /* previously unimplemented MAP_INHERIT */ -MAP_NOEXTEND :: 0x0100 /* for MAP_FILE, don't change file size */ -MAP_HASSEMAPHORE :: 0x0200 /* region may contain semaphores */ -MAP_NOCACHE :: 0x0400 /* don't cache pages for this mapping */ -MAP_JIT :: 0x0800 /* Allocate a region that will be used for JIT purposes */ - -// Mapping type -MAP_FILE :: 0x0000 /* map from file (default) */ -MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ - - -/* - * The MAP_RESILIENT_* flags can be used when the caller wants to map some - * possibly unreliable memory and be able to access it safely, possibly - * getting the wrong contents rather than raising any exception. - * For safety reasons, such mappings have to be read-only (PROT_READ access - * only). - * - * MAP_RESILIENT_CODESIGN: - * accessing this mapping will not generate code-signing violations, - * even if the contents are tainted. - * MAP_RESILIENT_MEDIA: - * accessing this mapping will not generate an exception if the contents - * are not available (unreachable removable or remote media, access beyond - * end-of-file, ...). Missing contents will be replaced with zeroes. - */ -MAP_RESILIENT_CODESIGN :: 0x2000 /* no code-signing failures */ -MAP_RESILIENT_MEDIA :: 0x4000 /* no backing-store failures */ - -MAP_32BIT :: 0x8000 /* Return virtual addresses <4G only */ - -// Flags used to support translated processes. -MAP_TRANSLATED_ALLOW_EXECUTE :: 0x20000 /* allow execute in translated processes */ -MAP_UNIX03 :: 0x40000 /* UNIX03 compliance */ - -// Process memory locking -MCL_CURRENT :: 0x0001 /* [ML] Lock only current memory */ -MCL_FUTURE :: 0x0002 /* [ML] Lock all future memory as well */ - -MADV_NORMAL :: 0 /* [MC1] no further special treatment */ -MADV_RANDOM :: 1 /* [MC1] expect random page refs */ -MADV_SEQUENTIAL :: 2 /* [MC1] expect sequential page refs */ -MADV_WILLNEED :: 3 /* [MC1] will need these pages */ -MADV_DONTNEED :: 4 /* [MC1] dont need these pages */ -MADV_FREE :: 5 /* pages unneeded, discard contents */ -MADV_ZERO_WIRED_PAGES :: 6 /* zero the wired pages that have not been unwired before the entry is deleted */ -MADV_FREE_REUSABLE :: 7 /* pages can be reused (by anyone) */ -MADV_FREE_REUSE :: 8 /* caller wants to reuse those pages */ -MADV_CAN_REUSE :: 9 -MADV_PAGEOUT :: 10 /* page out now (internal only) */ - -// msync() flags -MS_ASYNC :: 0x0001 /* [MF|SIO] return immediately */ -MS_INVALIDATE :: 0x0002 /* [MF|SIO] invalidate all cached data */ -MS_SYNC :: 0x0010 /* [MF|SIO] msync synchronously */ -MS_KILLPAGES :: 0x0004 /* invalidate pages, leave mapped */ -MS_DEACTIVATE :: 0x0008 /* deactivate pages, leave mapped */ - -// Return bits from mincore -MINCORE_INCORE :: 0x1 /* Page is incore */ -MINCORE_REFERENCED :: 0x2 /* Page has been referenced by us */ -MINCORE_MODIFIED :: 0x4 /* Page has been modified by us */ -MINCORE_REFERENCED_OTHER :: 0x8 /* Page has been referenced */ -MINCORE_MODIFIED_OTHER :: 0x10 /* Page has been modified */ -MINCORE_PAGED_OUT :: 0x20 /* Page has been paged out */ -MINCORE_COPIED :: 0x40 /* Page has been copied */ -MINCORE_ANONYMOUS :: 0x80 /* Page belongs to an anonymous object */ - -// Allocation failure result -MAP_FAILED : rawptr = rawptr(~uintptr(0)) - -foreign libc { - @(link_name="mlockall") _mlockall :: proc(flags: c.int) -> c.int --- - @(link_name="munlockall") _munlockall :: proc() -> c.int --- - @(link_name="mlock") _mlock :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="mmap") _mmap :: proc(addr: rawptr, len: c.size_t, prot: c.int, flags: c.int, fd: c.int, offset: int) -> rawptr --- - @(link_name="mprotect") _mprotect :: proc(addr: rawptr, len: c.size_t, prot: c.int) -> c.int --- - @(link_name="msync") _msync :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="munlock") _munlock :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="munmap") _munmap :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="shm_open") _shm_open :: proc(name: cstring, oflag: c.int, #c_vararg args: ..any) -> c.int --- - @(link_name="shm_unlink") _shm_unlink :: proc(name: cstring) -> c.int --- - @(link_name="posix_madvise") _posix_madvise :: proc(addr: rawptr, len: c.size_t, advice: c.int) -> c.int --- - @(link_name="madvise") _madvise :: proc(addr: rawptr, len: c.size_t, advice: c.int) -> c.int --- - @(link_name="mincore") _mincore :: proc(addr: rawptr, len: c.size_t, vec: cstring) -> c.int --- - @(link_name="minherit") _minherit :: proc(addr: rawptr, len: c.size_t, inherit: c.int) -> c.int --- -} - - -_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { - result := _mmap(nil, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) - if result == MAP_FAILED { - return nil, .Out_Of_Memory - } - return ([^]byte)(uintptr(result))[:size], nil -} - -_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { - result := _mprotect(data, size, PROT_READ|PROT_WRITE) - if result != 0 { - return .Out_Of_Memory - } - return nil -} -_decommit :: proc "contextless" (data: rawptr, size: uint) { - _mprotect(data, size, PROT_NONE) - _madvise(data, size, MADV_FREE) -} -_release :: proc "contextless" (data: rawptr, size: uint) { - _munmap(data, size) -} -_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { - pflags: c.int - pflags = PROT_NONE - if .Read in flags { pflags |= PROT_READ } - if .Write in flags { pflags |= PROT_WRITE } - if .Execute in flags { pflags |= PROT_EXEC } - err := _mprotect(data, size, pflags) - return err == 0 -} - - -_platform_memory_init :: proc() { - DEFAULT_PAGE_SIZE = 4096 - - // is power of two - assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) -} - - -_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { - prot, mflags: c.int - if .Read in flags { - prot |= PROT_READ - } - if .Write in flags { - prot |= PROT_WRITE - } - mflags |= MAP_SHARED - addr := _mmap(nil, c.size_t(size), prot, mflags, i32(fd), 0) - if addr == nil { - return nil, .Map_Failure - } - return ([^]byte)(addr)[:size], nil -} diff --git a/core/mem/virtual/virtual_other.odin b/core/mem/virtual/virtual_other.odin index 96d9683c4..4fcb61b04 100644 --- a/core/mem/virtual/virtual_other.odin +++ b/core/mem/virtual/virtual_other.odin @@ -1,5 +1,8 @@ //+private //+build !darwin +//+build !freebsd +//+build !openbsd +//+build !netbsd //+build !linux //+build !windows package mem_virtual diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin new file mode 100644 index 000000000..035763466 --- /dev/null +++ b/core/mem/virtual/virtual_posix.odin @@ -0,0 +1,71 @@ +//+build darwin, netbsd, freebsd, openbsd +//+private +package mem_virtual + +import "core:sys/posix" + +// Define non-posix needed flags: +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ + + MADV_FREE :: 5 /* pages unneeded, discard contents */ +} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { + MAP_ANONYMOUS :: 0x1000 + + MADV_FREE :: 6 +} + +_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { + flags := posix.Map_Flags{ .PRIVATE } + transmute(posix.Map_Flags)i32(MAP_ANONYMOUS) + result := posix.mmap(nil, size, {}, flags) + if result == posix.MAP_FAILED { + return nil, .Out_Of_Memory + } + + return ([^]byte)(uintptr(result))[:size], nil +} + +_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { + if posix.mprotect(data, size, { .READ, .WRITE }) != .OK { + return .Out_Of_Memory + } + + return nil +} + +_decommit :: proc "contextless" (data: rawptr, size: uint) { + posix.mprotect(data, size, {}) + posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) +} + +_release :: proc "contextless" (data: rawptr, size: uint) { + posix.munmap(data, size) +} + +_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { + #assert(i32(posix.Prot_Flag_Bits.READ) == i32(Protect_Flag.Read)) + #assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Protect_Flag.Write)) + #assert(i32(posix.Prot_Flag_Bits.EXEC) == i32(Protect_Flag.Execute)) + + return posix.mprotect(data, size, transmute(posix.Prot_Flags)flags) == .OK +} + +_platform_memory_init :: proc() { + // NOTE: `posix.PAGESIZE` due to legacy reasons could be wrong so we use `sysconf`. + size := posix.sysconf(._PAGESIZE) + DEFAULT_PAGE_SIZE = uint(max(size, posix.PAGESIZE)) + + // is power of two + assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) +} + +_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { + #assert(i32(posix.Prot_Flag_Bits.READ) == i32(Map_File_Flag.Read)) + #assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Map_File_Flag.Write)) + + addr := posix.mmap(nil, uint(size), transmute(posix.Prot_Flags)flags, { .SHARED }, posix.FD(fd)) + if addr == posix.MAP_FAILED || addr == nil { + return nil, .Map_Failure + } + return ([^]byte)(addr)[:size], nil +} diff --git a/core/net/addr.odin b/core/net/addr.odin index eed3fb3b9..1972d8c22 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +// +build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strconv" @@ -742,4 +744,4 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D get_network_interfaces :: proc() -> []Address { // TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces. return nil -} \ No newline at end of file +} diff --git a/core/net/common.odin b/core/net/common.odin index db969eab8..ed255356e 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +// +build windows, linux, darwin, freebsd package net /* @@ -13,12 +13,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "base:runtime" @@ -70,6 +72,8 @@ Network_Error :: union #shared_nil { DNS_Error, } +#assert(size_of(Network_Error) == 8) + General_Error :: enum u32 { None = 0, Unable_To_Enumerate_Network_Interfaces = 1, @@ -78,7 +82,7 @@ General_Error :: enum u32 { // `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error. Platform_Error :: enum u32 {} -Parse_Endpoint_Error :: enum { +Parse_Endpoint_Error :: enum u32 { None = 0, Bad_Port = 1, Bad_Address, @@ -413,4 +417,4 @@ DNS_Record_Header :: struct #packed { DNS_Host_Entry :: struct { name: string, addr: Address, -} \ No newline at end of file +} diff --git a/core/net/dns.odin b/core/net/dns.odin index 408fd201b..e82b54262 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +// +build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:mem" @@ -30,7 +32,7 @@ when ODIN_OS == .Windows { resolv_conf = "", hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", } -} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { +} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ resolv_conf = "/etc/resolv.conf", hosts_file = "/etc/hosts", diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin index e9b7bd066..0448b8d9e 100644 --- a/core/net/dns_unix.odin +++ b/core/net/dns_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin +//+build linux, darwin, freebsd package net /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -9,12 +9,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index ccec7ea4b..1b7fe7196 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" @@ -126,33 +128,37 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : append(&recs, record) case .SRV: - target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on - priority := int(r.Data.SRV.wPriority) - weight := int(r.Data.SRV.wWeight) - port := int(r.Data.SRV.wPort) - // NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname' // The record name is the name of the record. // Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up // by making this request in the first place. - // NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name. - // It's already cloned, after all. I wouldn't put them on the temp allocator like this. + service_name, protocol_name: string - parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator) - if len(parts) != 3 { + s := base_record.record_name + i := strings.index_byte(s, '.') + if i > -1 { + service_name = s[:i] + s = s[len(service_name) + 1:] + } else { + continue + } + + i = strings.index_byte(s, '.') + if i > -1 { + protocol_name = s[:i] + } else { continue } - service_name, protocol_name := parts[0], parts[1] append(&recs, DNS_Record_SRV { base = base_record, - target = target, - port = port, + target = strings.clone(string(r.Data.SRV.pNameTarget)), // The target hostname/address that the service can be found on + port = int(r.Data.SRV.wPort), service_name = service_name, protocol_name = protocol_name, - priority = priority, - weight = weight, + priority = int(r.Data.SRV.wPriority), + weight = int(r.Data.SRV.wWeight), }) } diff --git a/core/net/doc.odin b/core/net/doc.odin index 0f1b33172..ed720c0ae 100644 --- a/core/net/doc.odin +++ b/core/net/doc.odin @@ -2,45 +2,45 @@ Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ /* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. +Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. +For other protocols and their features, see subdirectories of this package. - Features: - - Supports Windows, Linux and OSX. - - Opening and closing of TCP and UDP sockets. - - Sending to and receiving from these sockets. - - DNS name lookup, using either the OS or our own resolver. +Features: +- Supports Windows, Linux and OSX. +- Opening and closing of TCP and UDP sockets. +- Sending to and receiving from these sockets. +- DNS name lookup, using either the OS or our own resolver. - Planned: - - Nonblocking IO - - `Connection` struct - A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle. - - IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures. - - Use `context.temp_allocator` instead of stack-based arenas? - And check it's the default temp allocator or can give us 4 MiB worth of memory - without punting to the main allocator by comparing their addresses in an @(init) procedure. - Panic if this assumption is not met. +Planned: +- Nonblocking IO +- `Connection` struct; A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle. +- IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures. +- Use `context.temp_allocator` instead of stack-based arenas? +And check it's the default temp allocator or can give us 4 MiB worth of memory +without punting to the main allocator by comparing their addresses in an @(init) procedure. +Panic if this assumption is not met. +- Document assumptions about libc usage (or avoidance thereof) for each platform. - - Document assumptions about libc usage (or avoidance thereof) for each platform. +Assumptions: +For performance reasons this package relies on the `context.temp_allocator` in some places. - Assumptions: - - For performance reasons this package relies on the `context.temp_allocator` in some places. +You can replace the default `context.temp_allocator` with your own as long as it meets +this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed. - You can replace the default `context.temp_allocator` with your own as long as it meets - this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed. - - If this expectation is not met, the package's @(init) procedure will attempt to detect - this and panic to avoid temp allocations prematurely overwriting data and garbling results, - or worse. This means that should you replace the temp allocator with an insufficient one, - we'll do our best to loudly complain the first time you try it. +If this expectation is not met, the package's @(init) procedure will attempt to detect +this and panic to avoid temp allocations prematurely overwriting data and garbling results, +or worse. This means that should you replace the temp allocator with an insufficient one, +we'll do our best to loudly complain the first time you try it. */ -package net \ No newline at end of file +package net diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index 3116af0ab..f2a0d6262 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin new file mode 100644 index 000000000..4830d1c03 --- /dev/null +++ b/core/net/errors_freebsd.odin @@ -0,0 +1,217 @@ +//+build freebsd +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code +*/ + +import "core:c" +import "core:sys/freebsd" + +Create_Socket_Error :: enum c.int { + None = 0, + Access_Denied = cast(c.int)freebsd.Errno.EACCES, + Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, + Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, + Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, + No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, + Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM, + Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT, + Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE, +} + +Dial_Error :: enum c.int { + None = 0, + Port_Required = -1, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, + Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, + Already_Connected = cast(c.int)freebsd.Errno.EISCONN, + Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT, + Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED, + // `Refused` alias for `core:net` tests. + // The above default name `Refused_By_Remote_Host` is more explicit. + Refused = Refused_By_Remote_Host, + Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET, + Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH, + Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, + Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, + Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS, + Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, + Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY, + Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES, + Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, + + // NOTE: There are additional connect() error possibilities, but they are + // strictly for addresses in the UNIX domain. +} + +Bind_Error :: enum c.int { + None = 0, + Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + + // NOTE: bind() can also return EINVAL if the underlying `addrlen` is an + // invalid length for the address family. This shouldn't happen for the net + // package, but it's worth noting. + Already_Bound = cast(c.int)freebsd.Errno.EINVAL, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, + Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, + Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT, + Protected_Address = cast(c.int)freebsd.Errno.EACCES, + Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + + // NOTE: There are additional bind() error possibilities, but they are + // strictly for addresses in the UNIX domain. +} + +Listen_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ, + Already_Connected = cast(c.int)freebsd.Errno.EINVAL, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP, +} + +Accept_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Interrupted = cast(c.int)freebsd.Errno.EINTR, + Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, + Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL, + Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT, + + // NOTE: This is the same as EWOULDBLOCK. + No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN, + // `Would_Block` alias for `core:net` tests. + Would_Block = cast(c.int)freebsd.Errno.EAGAIN, + + New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED, +} + +TCP_Recv_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + + // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), + // but I'm including them for completeness's sake. + Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE, + Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, + + Timeout = cast(c.int)freebsd.Errno.EAGAIN, + Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, + Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, +} + +UDP_Recv_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + + // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), + // but I'm including them for completeness's sake. + Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE, + Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, + + Timeout = cast(c.int)freebsd.Errno.EAGAIN, + Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, + Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, +} + +TCP_Send_Error :: enum c.int { + None = 0, + Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + + Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE, + + /* The socket is marked non-blocking, or MSG_DONTWAIT is + specified, and the requested operation would block. */ + Would_Block = cast(c.int)freebsd.Errno.EAGAIN, + + /* NOTE: This error arises for two distinct reasons: + + 1. The system was unable to allocate an internal buffer. + The operation may succeed when buffers become available. + + 2. The output queue for a network interface was full. + This generally indicates that the interface has stopped + sending, but may be caused by transient congestion. + */ + No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, + + Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, + Already_Connected = cast(c.int)freebsd.Errno.EISCONN, + ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED, + Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN, + Network_Down = cast(c.int)freebsd.Errno.ENETDOWN, + Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, + Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE, +} + +// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware. +UDP_Send_Error :: distinct TCP_Send_Error + +Shutdown_Manner :: enum c.int { + Receive = cast(c.int)freebsd.Shutdown_Method.RD, + Send = cast(c.int)freebsd.Shutdown_Method.WR, + Both = cast(c.int)freebsd.Shutdown_Method.RDWR, +} + +Shutdown_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, +} + +Socket_Option_Error :: enum c.int { + None = 0, + Value_Out_Of_Range = -1, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT, + Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + // This error can arise for many different reasons. + Invalid_Value = cast(c.int)freebsd.Errno.EINVAL, + System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM, + Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS, +} + +Set_Blocking_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY, +} diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 2370dd0d8..9047b4020 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -10,6 +10,7 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: @@ -17,6 +18,7 @@ package net Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation flysand: Move dependency from core:linux.Errno to core:sys/linux + Feoramund: FreeBSD platform code */ import "core:c" diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index f63f17f4e..ae928a05c 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" diff --git a/core/net/interface.odin b/core/net/interface.odin index df7d0223e..90444fb63 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +// +build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index e2a9a73ca..7a33682de 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -10,13 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation - + Feoramund: FreeBSD platform code */ import "core:os" diff --git a/core/net/interface_freebsd.odin b/core/net/interface_freebsd.odin new file mode 100644 index 000000000..a9a125299 --- /dev/null +++ b/core/net/interface_freebsd.odin @@ -0,0 +1,177 @@ +//+build freebsd +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code +*/ + +import "core:c" +import "core:strings" +import "core:sys/freebsd" + +@(private) +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { + // This is a simplified implementation of `getifaddrs` from the FreeBSD + // libc using only Odin and syscalls. + context.allocator = allocator + + mib := [6]freebsd.MIB_Identifier { + .CTL_NET, + cast(freebsd.MIB_Identifier)freebsd.Protocol_Family.ROUTE, + freebsd.MIB_Identifier(0), + freebsd.MIB_Identifier(0), + .NET_RT_IFLISTL, + freebsd.MIB_Identifier(0), + } + + // Figure out how much space we need. + needed: c.size_t = --- + + errno := freebsd.sysctl(mib[:], nil, &needed, nil, 0) + if errno != nil { + return nil, .Unable_To_Enumerate_Network_Interfaces + } + + // Allocate and get the entries. + buf, alloc_err := make([]byte, needed) + if alloc_err != nil { + return nil, .Unable_To_Enumerate_Network_Interfaces + } + defer delete(buf) + + errno = freebsd.sysctl(mib[:], &buf[0], &needed, nil, 0) + if errno != nil { + return nil, .Unable_To_Enumerate_Network_Interfaces + } + + // Build the interfaces with each message. + if_builder: [dynamic]Network_Interface + for message_pointer: uintptr = 0; message_pointer < cast(uintptr)needed; /**/ { + rtm := cast(^freebsd.Route_Message_Header)&buf[message_pointer] + if rtm.version != freebsd.RTM_VERSION { + continue + } + + #partial switch rtm.type { + case .IFINFO: + ifm := cast(^freebsd.Interface_Message_Header_Len)&buf[message_pointer] + if .IFP not_in ifm.addrs { + // No name available. + break + } + + dl := cast(^freebsd.Socket_Address_Data_Link)&buf[message_pointer + cast(uintptr)ifm.len] + + if_data := cast(^freebsd.Interface_Data)&buf[message_pointer + cast(uintptr)ifm.data_off] + + // This is done this way so the different message types can + // dynamically build a `Network_Interface`. + resize(&if_builder, max(len(if_builder), 1 + cast(int)ifm.index)) + interface := if_builder[ifm.index] + + interface.adapter_name = strings.clone_from_bytes(dl.data[0:dl.nlen]) + interface.mtu = if_data.mtu + + switch if_data.link_state { + case .UNKNOWN: /* Do nothing; the default value is valid. */ + case .UP: interface.link.state |= { .Up } + case .DOWN: interface.link.state |= { .Down } + } + + // TODO: Uncertain if these are equivalent: + // interface.link.transmit_speed = if_data.baudrate + // interface.link.receive_speed = if_data.baudrate + + if dl.type == .LOOP { + interface.link.state |= { .Loopback } + } else { + interface.physical_address = physical_address_to_string(dl.data[dl.nlen:][:6]) + } + + if_builder[ifm.index] = interface + + case .NEWADDR: + RTA_MASKS :: freebsd.Route_Address_Flags { .IFA, .NETMASK } + ifam := cast(^freebsd.Interface_Address_Message_Header_Len)&buf[message_pointer] + if ifam.addrs & RTA_MASKS == {} { + break + } + + resize(&if_builder, max(len(if_builder), 1 + cast(int)ifam.index)) + interface := if_builder[ifam.index] + + address_pointer := message_pointer + cast(uintptr)ifam.len + + lease: Lease + address_set: bool + for address_type in ifam.addrs { + ptr := cast(^freebsd.Socket_Address_Basic)&buf[address_pointer] + + #partial switch address_type { + case .IFA: + #partial switch ptr.family { + case .INET: + real := cast(^freebsd.Socket_Address_Internet)ptr + lease.address = cast(IP4_Address)real.addr.addr8 + address_set = true + case .INET6: + real := cast(^freebsd.Socket_Address_Internet6)ptr + lease.address = cast(IP6_Address)real.addr.addr16 + address_set = true + } + case .NETMASK: + #partial switch ptr.family { + case .INET: + real := cast(^freebsd.Socket_Address_Internet)ptr + lease.netmask = cast(Netmask)cast(IP4_Address)real.addr.addr8 + case .INET6: + real := cast(^freebsd.Socket_Address_Internet6)ptr + lease.netmask = cast(Netmask)cast(IP6_Address)real.addr.addr16 + } + } + + SALIGN : u8 : size_of(c.long) - 1 + address_advance: uintptr = --- + if ptr.len > 0 { + address_advance = cast(uintptr)((ptr.len + SALIGN) & ~SALIGN) + } else { + address_advance = cast(uintptr)(SALIGN + 1) + } + + address_pointer += address_advance + } + + if address_set { + append(&interface.unicast, lease) + } + + if_builder[ifam.index] = interface + } + + message_pointer += cast(uintptr)rtm.msglen + } + + // Remove any interfaces that were allocated but had no name. + #no_bounds_check for i := len(if_builder) - 1; i >= 0; i -= 1 { + if len(if_builder[i].adapter_name) == 0 { + ordered_remove(&if_builder, i) + } + } + + return if_builder[:], nil +} diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index 7c99cf23b..c6df8f0a2 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code This file uses `getifaddrs` libc call to enumerate interfaces. TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc. diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index b9abcff48..67da6d034 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import sys "core:sys/windows" diff --git a/core/net/socket.odin b/core/net/socket.odin index 5f137401e..e36c67d21 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +// +build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022-2023 Tetralux Copyright 2022-2023 Colin Davidson Copyright 2022-2023 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket { diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index 10069963a..a56d36de6 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" @@ -194,8 +196,12 @@ _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, res_err := os.send(os.Socket(skt), remaining, 0) - if res_err != nil { + res, res_err := os.send(os.Socket(skt), remaining, os.MSG_NOSIGNAL) + if res_err == os.EPIPE { + // EPIPE arises if the socket has been closed remotely. + err = TCP_Send_Error.Connection_Closed + return + } else if res_err != nil { err = TCP_Send_Error(os.is_platform_error(res_err) or_else -1) return } @@ -210,8 +216,12 @@ _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, res_err := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) - if res_err != nil { + res, res_err := os.sendto(os.Socket(skt), remaining, os.MSG_NOSIGNAL, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) + if res_err == os.EPIPE { + // EPIPE arises if the socket has been closed remotely. + err = UDP_Send_Error.Not_Socket + return + } else if res_err != nil { err = UDP_Send_Error(os.is_platform_error(res_err) or_else -1) return } diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin new file mode 100644 index 000000000..00da5ec06 --- /dev/null +++ b/core/net/socket_freebsd.odin @@ -0,0 +1,404 @@ +//+build freebsd +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code +*/ + +import "core:c" +import "core:sys/freebsd" +import "core:time" + +Fd :: freebsd.Fd + +Socket_Option :: enum c.int { + // TODO: Test and implement more socket options. + // DEBUG + Reuse_Address = cast(c.int)freebsd.Socket_Option.REUSEADDR, + Keep_Alive = cast(c.int)freebsd.Socket_Option.KEEPALIVE, + // DONTROUTE + Broadcast = cast(c.int)freebsd.Socket_Option.BROADCAST, + Use_Loopback = cast(c.int)freebsd.Socket_Option.USELOOPBACK, + Linger = cast(c.int)freebsd.Socket_Option.LINGER, + Out_Of_Bounds_Data_Inline = cast(c.int)freebsd.Socket_Option.OOBINLINE, + Reuse_Port = cast(c.int)freebsd.Socket_Option.REUSEPORT, + // TIMESTAMP + No_SIGPIPE_From_EPIPE = cast(c.int)freebsd.Socket_Option.NOSIGPIPE, + // ACCEPTFILTER + // BINTIME + // NO_OFFLOAD + // NO_DDP + Reuse_Port_Load_Balancing = cast(c.int)freebsd.Socket_Option.REUSEPORT_LB, + // RERROR + + Send_Buffer_Size = cast(c.int)freebsd.Socket_Option.SNDBUF, + Receive_Buffer_Size = cast(c.int)freebsd.Socket_Option.RCVBUF, + // SNDLOWAT + // RCVLOWAT + Send_Timeout = cast(c.int)freebsd.Socket_Option.SNDTIMEO, + Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO, +} + +@(private) +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { + sys_family: freebsd.Protocol_Family = --- + sys_protocol: freebsd.Protocol = --- + sys_socket_type: freebsd.Socket_Type = --- + + switch family { + case .IP4: sys_family = .INET + case .IP6: sys_family = .INET6 + } + + switch protocol { + case .TCP: sys_protocol = .TCP; sys_socket_type = .STREAM + case .UDP: sys_protocol = .UDP; sys_socket_type = .DGRAM + } + + new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol) + if errno != nil { + err = cast(Create_Socket_Error)errno + return + } + + // NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE + // error is raised during the writing of a socket that may be closed. + // This behavior is unlikely to be expected by general users. + // + // There are two workarounds. One is to apply the .NOSIGNAL flag when using + // the `sendto` syscall. However, that would prevent users of this library + // from re-enabling the SIGPIPE-raising functionality, if they really + // wanted it. + // + // So I have disabled it here with this socket option for all sockets. + truth: b32 = true + errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth)) + if errno != nil { + err = cast(Socket_Option_Error)errno + return + } + + switch protocol { + case .TCP: return cast(TCP_Socket)new_socket, nil + case .UDP: return cast(UDP_Socket)new_socket, nil + } + + return +} + +@(private) +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { + if endpoint.port == 0 { + return 0, .Port_Required + } + + family := family_from_endpoint(endpoint) + new_socket := create_socket(family, .TCP) or_return + socket = new_socket.(TCP_Socket) + + sockaddr := _endpoint_to_sockaddr(endpoint) + errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) + if errno != nil { + err = cast(Dial_Error)errno + return + } + + return +} + +@(private) +_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { + sockaddr := _endpoint_to_sockaddr(ep) + real_socket := any_socket_to_socket(socket) + errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) + if errno != nil { + err = cast(Bind_Error)errno + } + return +} + +@(private) +_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { + family := family_from_endpoint(interface_endpoint) + new_socket := create_socket(family, .TCP) or_return + socket = new_socket.(TCP_Socket) + + bind(socket, interface_endpoint) or_return + + errno := freebsd.listen(cast(Fd)socket, backlog) + if errno != nil { + err = cast(Listen_Error)errno + return + } + + return +} + +@(private) +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { + sockaddr: freebsd.Socket_Address_Storage + + result, errno := freebsd.accept(cast(Fd)sock, &sockaddr) + if errno != nil { + err = cast(Accept_Error)errno + return + } + + client = cast(TCP_Socket)result + source = _sockaddr_to_endpoint(&sockaddr) + return +} + +@(private) +_close :: proc(socket: Any_Socket) { + real_socket := cast(Fd)any_socket_to_socket(socket) + // TODO: This returns an error number, but the `core:net` interface does not handle it. + _ = freebsd.close(real_socket) +} + +@(private) +_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { + if len(buf) == 0 { + return + } + result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE) + if errno != nil { + err = cast(TCP_Recv_Error)errno + return + } + return result, nil +} + +@(private) +_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { + if len(buf) == 0 { + return + } + from: freebsd.Socket_Address_Storage + + result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from) + if errno != nil { + err = cast(UDP_Recv_Error)errno + return + } + return result, _sockaddr_to_endpoint(&from), nil +} + +@(private) +_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + + result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE) + if errno != nil { + err = cast(TCP_Send_Error)errno + return + } + bytes_written += result + } + return +} + +@(private) +_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { + toaddr := _endpoint_to_sockaddr(to) + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + + result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr) + if errno != nil { + err = cast(UDP_Send_Error)errno + return + } + bytes_written += result + } + return +} + +@(private) +_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + real_socket := cast(Fd)any_socket_to_socket(socket) + errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner) + if errno != nil { + return cast(Shutdown_Error)errno + } + return +} + +@(private) +_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { + // NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32 + // bits for a boolean socket option value. Nothing less will work. + bool_value: b32 + // TODO: Assuming no larger than i32, but the system may accept i64. + int_value: i32 + timeval_value: freebsd.timeval + + ptr: rawptr + len: freebsd.socklen_t + + switch option { + case + .Reuse_Address, + .Keep_Alive, + .Broadcast, + .Use_Loopback, + .Out_Of_Bounds_Data_Inline, + .Reuse_Port, + .No_SIGPIPE_From_EPIPE, + .Reuse_Port_Load_Balancing: + switch real in value { + case bool: bool_value = cast(b32)real + case b8: bool_value = cast(b32)real + case b16: bool_value = cast(b32)real + case b32: bool_value = real + case b64: bool_value = cast(b32)real + case: + panic("set_option() value must be a boolean here", loc) + } + ptr = &bool_value + len = size_of(bool_value) + case + .Linger, + .Send_Timeout, + .Receive_Timeout: + t, ok := value.(time.Duration) + if !ok { + panic("set_option() value must be a time.Duration here", loc) + } + + micros := cast(freebsd.time_t)time.duration_microseconds(t) + timeval_value.usec = cast(freebsd.suseconds_t)micros % 1e6 + timeval_value.sec = (micros - cast(freebsd.time_t)timeval_value.usec) / 1e6 + + ptr = &timeval_value + len = size_of(timeval_value) + case + .Receive_Buffer_Size, + .Send_Buffer_Size: + switch real in value { + case i8: int_value = cast(i32)real + case u8: int_value = cast(i32)real + case i16: int_value = cast(i32)real + case u16: int_value = cast(i32)real + case i32: int_value = real + case u32: + if real > u32(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case i64: + if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case u64: + if real > u64(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case i128: + if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case u128: + if real > u128(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case int: + if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case uint: + if real > uint(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case: + panic("set_option() value must be an integer here", loc) + } + ptr = &int_value + len = size_of(int_value) + case: + unimplemented("set_option() option not yet implemented", loc) + } + + real_socket := any_socket_to_socket(socket) + errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len) + if errno != nil { + return cast(Socket_Option_Error)errno + } + + return nil +} + +@(private) +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { + real_socket := any_socket_to_socket(socket) + + flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket) + if errno != nil { + return cast(Set_Blocking_Error)errno + } + + if should_block { + flags &= ~{ .NONBLOCK } + } else { + flags |= { .NONBLOCK } + } + + errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags) + if errno != nil { + return cast(Set_Blocking_Error)errno + } + + return +} + +@(private) +_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: freebsd.Socket_Address_Storage) { + switch addr in ep.address { + case IP4_Address: + (cast(^freebsd.Socket_Address_Internet)(&sockaddr))^ = { + len = size_of(freebsd.Socket_Address_Internet), + family = .INET, + port = cast(freebsd.in_port_t)ep.port, + addr = transmute(freebsd.IP4_Address)addr, + } + case IP6_Address: + (cast(^freebsd.Socket_Address_Internet6)(&sockaddr))^ = { + len = size_of(freebsd.Socket_Address_Internet), + family = .INET6, + port = cast(freebsd.in_port_t)ep.port, + addr = transmute(freebsd.IP6_Address)addr, + } + } + return +} + +@(private) +_sockaddr_to_endpoint :: proc(native_addr: ^freebsd.Socket_Address_Storage) -> (ep: Endpoint) { + #partial switch native_addr.family { + case .INET: + addr := cast(^freebsd.Socket_Address_Internet)native_addr + ep = { + address = cast(IP4_Address)addr.addr.addr8, + port = cast(int)addr.port, + } + case .INET6: + addr := cast(^freebsd.Socket_Address_Internet6)native_addr + ep = { + address = cast(IP6_Address)addr.addr.addr16, + port = cast(int)addr.port, + } + case: + panic("native_addr is neither an IP4 or IP6 address") + } + return +} diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 350d3947c..52f328814 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -10,6 +10,7 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: @@ -17,6 +18,7 @@ package net Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation flysand: Move dependency from core:os to core:sys/linux + Feoramund: FreeBSD platform code */ import "core:c" @@ -31,8 +33,8 @@ Socket_Option :: enum c.int { Linger = c.int(linux.Socket_Option.LINGER), Receive_Buffer_Size = c.int(linux.Socket_Option.RCVBUF), Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF), - Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO_NEW), - Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO_NEW), + Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO), + Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO), } // Wrappers and unwrappers for system-native types diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index eef0df583..8ee75bc3b 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" diff --git a/core/net/url.odin b/core/net/url.odin index 16aa57ec5..aadcf5e48 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -8,12 +8,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 31e8fdd53..9088da0ea 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -27,6 +27,8 @@ Proc_Calling_Convention :: union { Node_State_Flag :: enum { Bounds_Check, No_Bounds_Check, + Type_Assert, + No_Type_Assert, } Node_State_Flags :: distinct bit_set[Node_State_Flag] @@ -861,6 +863,7 @@ Bit_Field_Field :: struct { name: ^Expr, type: ^Expr, bit_size: ^Expr, + tag: tokenizer.Token, comments: ^Comment_Group, } diff --git a/core/odin/ast/walk.odin b/core/odin/ast/walk.odin index 7304f237c..cba040875 100644 --- a/core/odin/ast/walk.odin +++ b/core/odin/ast/walk.odin @@ -61,7 +61,7 @@ walk :: proc(v: ^Visitor, node: ^Node) { return } - switch n in &node.derived { + switch n in node.derived { case ^File: if n.docs != nil { walk(v, n.docs) diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin new file mode 100644 index 000000000..b12c3b5fd --- /dev/null +++ b/core/odin/parser/file_tags.odin @@ -0,0 +1,239 @@ +package odin_parser + +import "base:runtime" +import "core:strings" +import "core:reflect" + +import "../ast" + +Private_Flag :: enum { + Public, + Package, + File, +} + +Build_Kind :: struct { + os: runtime.Odin_OS_Types, + arch: runtime.Odin_Arch_Types, +} + +File_Tags :: struct { + build_project_name: [][]string, + build: []Build_Kind, + private: Private_Flag, + ignore: bool, + lazy: bool, + no_instrumentation: bool, +} + +@require_results +get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type { + fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type) + for os in fields { + if strings.equal_fold(os.name, str) { + return runtime.Odin_OS_Type(os.value) + } + } + return .Unknown +} +@require_results +get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { + fields := reflect.enum_fields_zipped(runtime.Odin_Arch_Type) + for os in fields { + if strings.equal_fold(os.name, str) { + return runtime.Odin_Arch_Type(os.value) + } + } + return .Unknown +} + +@require_results +parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags: File_Tags) { + context.allocator = allocator + + if file.docs == nil { + return + } + + next_char :: proc(src: string, i: ^int) -> (ch: u8) { + if i^ < len(src) { + ch = src[i^] + } + i^ += 1 + return + } + skip_whitespace :: proc(src: string, i: ^int) { + for { + switch next_char(src, i) { + case ' ', '\t': + continue + case: + i^ -= 1 + return + } + } + } + scan_value :: proc(src: string, i: ^int) -> string { + start := i^ + for { + switch next_char(src, i) { + case ' ', '\t', '\n', '\r', 0, ',': + i^ -= 1 + return src[start:i^] + case: + continue + } + } + } + + build_kinds: [dynamic]Build_Kind + defer shrink(&build_kinds) + + build_project_name_strings: [dynamic]string + defer shrink(&build_project_name_strings) + + build_project_names: [dynamic][]string + defer shrink(&build_project_names) + + for comment in file.docs.list { + if len(comment.text) < 3 || comment.text[:2] != "//" { + continue + } + text := comment.text[2:] + i := 0 + + skip_whitespace(text, &i) + + if next_char(text, &i) == '+' { + switch scan_value(text, &i) { + case "ignore": + tags.ignore = true + case "lazy": + tags.lazy = true + case "no-instrumentation": + tags.no_instrumentation = true + case "private": + skip_whitespace(text, &i) + switch scan_value(text, &i) { + case "file": + tags.private = .File + case "package", "": + tags.private = .Package + } + case "build-project-name": + groups_loop: for { + index_start := len(build_project_name_strings) + + defer append(&build_project_names, build_project_name_strings[index_start:]) + + for { + skip_whitespace(text, &i) + name_start := i + + switch next_char(text, &i) { + case 0, '\n': + i -= 1 + break groups_loop + case ',': + continue groups_loop + case '!': + // include ! in the name + case: + i -= 1 + } + + scan_value(text, &i) + append(&build_project_name_strings, text[name_start:i]) + } + + append(&build_project_names, build_project_name_strings[index_start:]) + } + case "build": + kinds_loop: for { + os_positive: runtime.Odin_OS_Types + os_negative: runtime.Odin_OS_Types + + arch_positive: runtime.Odin_Arch_Types + arch_negative: runtime.Odin_Arch_Types + + defer append(&build_kinds, Build_Kind{ + os = (os_positive == {} ? runtime.ALL_ODIN_OS_TYPES : os_positive) -os_negative, + arch = (arch_positive == {} ? runtime.ALL_ODIN_ARCH_TYPES : arch_positive)-arch_negative, + }) + + for { + skip_whitespace(text, &i) + + is_notted: bool + switch next_char(text, &i) { + case 0, '\n': + i -= 1 + break kinds_loop + case ',': + continue kinds_loop + case '!': + is_notted = true + case: + i -= 1 + } + + value := scan_value(text, &i) + + if value == "ignore" { + tags.ignore = true + } else if os := get_build_os_from_string(value); os != .Unknown { + if is_notted { + os_negative += {os} + } else { + os_positive += {os} + } + } else if arch := get_build_arch_from_string(value); arch != .Unknown { + if is_notted { + arch_negative += {arch} + } else { + arch_positive += {arch} + } + } + } + } + } + } + } + + tags.build = build_kinds[:] + tags.build_project_name = build_project_names[:] + + return +} + +Build_Target :: struct { + os: runtime.Odin_OS_Type, + arch: runtime.Odin_Arch_Type, + project_name: string, +} + +@require_results +match_build_tags :: proc(file_tags: File_Tags, target: Build_Target) -> bool { + + project_name_correct := len(target.project_name) == 0 || len(file_tags.build_project_name) == 0 + + for group in file_tags.build_project_name { + group_correct := true + for name in group { + if name[0] == '!' { + group_correct &&= target.project_name != name[1:] + } else { + group_correct &&= target.project_name == name + } + } + project_name_correct ||= group_correct + } + + os_and_arch_correct := len(file_tags.build) == 0 + + for kind in file_tags.build { + os_and_arch_correct ||= target.os in kind.os && target.arch in kind.arch + } + + return !file_tags.ignore && project_name_correct && os_and_arch_correct +} diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 24c85a19e..aab59c29d 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1438,6 +1438,15 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { stmt.state_flags += {.No_Bounds_Check} } return stmt + case "type_assert", "no_type_assert": + stmt := parse_stmt(p) + switch name { + case "type_assert": + stmt.state_flags += {.Type_Assert} + case "no_type_assert": + stmt.state_flags += {.No_Type_Assert} + } + return stmt case "partial": stmt := parse_stmt(p) #partial switch s in stmt.derived_stmt { @@ -2832,11 +2841,17 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { expect_token(p, .Or) bit_size := parse_expr(p, true) + tag: tokenizer.Token + if p.curr_tok.kind == .String { + tag = expect_token(p, .String) + } + field := ast.new(ast.Bit_Field_Field, name.pos, bit_size) field.name = name field.type = type field.bit_size = bit_size + field.tag = tag append(&fields, field) @@ -2845,7 +2860,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { close := expect_closing_brace_of_field_list(p) - bf := ast.new(ast.Bit_Field_Type, tok.pos, close.pos) + bf := ast.new(ast.Bit_Field_Type, tok.pos, end_pos(close)) bf.tok_pos = tok.pos bf.backing_type = backing_type diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin index 6f6bed36d..b472e89b7 100644 --- a/core/os/dir_unix.odin +++ b/core/os/dir_unix.odin @@ -5,10 +5,12 @@ 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 + dupfd := _dup(fd) or_return + + dirp := _fdopendir(dupfd) or_return defer _closedir(dirp) - dirpath := absolute_path_from_handle(fd) or_return + dirpath := absolute_path_from_handle(dupfd) or_return defer delete(dirpath) n := n diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index 3f6f781aa..375da6aff 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -192,6 +192,8 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { case 0: w = win32.FILE_BEGIN case 1: w = win32.FILE_CURRENT case 2: w = win32.FILE_END + case: + return 0, .Invalid_Whence } hi := i32(offset>>32) lo := i32(offset) @@ -223,11 +225,13 @@ file_size :: proc(fd: Handle) -> (i64, Error) { MAX_RW :: 1<<30 @(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] - } o := win32.OVERLAPPED{ @@ -247,11 +251,13 @@ pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { return int(done), e } @(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] - } o := win32.OVERLAPPED{ @@ -271,13 +277,6 @@ pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { /* read_at returns n: 0, err: 0 on EOF -on Windows, read_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - read_at(fd, bytes, 0) - read(fd, bytes) - -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: Error) { if offset < 0 { @@ -302,15 +301,6 @@ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { return } -/* -on Windows, write_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - write_at(fd, bytes, 0) - write(fd, bytes) - -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: Error) { if offset < 0 { return 0, .Invalid_Offset diff --git a/core/os/os.odin b/core/os/os.odin index 568c0a2aa..30b86d4cd 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -185,7 +185,9 @@ write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) - fd := open(name, flags, mode) or_return defer close(fd) - _ = write(fd, data) or_return + for n := 0; n < len(data); { + n += write(fd, data[n:]) or_return + } return nil } diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index ef73809b1..ddfe230be 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -62,3 +62,7 @@ TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime. return tmp, loc } +@(init, private) +init_thread_local_cleaner :: proc() { + runtime.add_thread_local_cleaner(temp_allocator_fini) +} diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin new file mode 100644 index 000000000..75f620d90 --- /dev/null +++ b/core/os/os2/dir_posix.odin @@ -0,0 +1,92 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" + +Read_Directory_Iterator_Impl :: struct { + dir: posix.DIR, + idx: int, + fullpath: [dynamic]byte, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + fimpl := (^File_Impl)(it.f.impl) + + index = it.impl.idx + it.impl.idx += 1 + + for { + entry := posix.readdir(it.impl.dir) + if entry == nil { + // NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator` + // There isn't a way to now know if it failed or if we are at the end. + return + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + sname := string(cname) + + stat: posix.stat_t + if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK { + // NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator` + // There isn't a way to now know if it failed or if we are at the end. + return + } + + n := len(fimpl.name)+1 + non_zero_resize(&it.impl.fullpath, n+len(sname)) + n += copy(it.impl.fullpath[n:], sname) + + fi = internal_stat(stat, string(it.impl.fullpath[:])) + ok = true + return + } +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + iter.f = f + iter.impl.idx = 0 + + iter.impl.fullpath.allocator = file_allocator() + append(&iter.impl.fullpath, impl.name) + append(&iter.impl.fullpath, "/") + defer if err != nil { delete(iter.impl.fullpath) } + + // `fdopendir` consumes the file descriptor so we need to `dup` it. + dupfd := posix.dup(impl.fd) + if dupfd == -1 { + err = _get_platform_error() + return + } + defer if err != nil { posix.close(dupfd) } + + iter.impl.dir = posix.fdopendir(dupfd) + if iter.impl.dir == nil { + err = _get_platform_error() + return + } + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it == nil || it.impl.dir == nil { + return + } + + posix.closedir(it.impl.dir) + delete(it.impl.fullpath) +} diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index 84f320095..1b9675064 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -138,4 +138,4 @@ _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { } 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_posix.odin b/core/os/os2/env_posix.odin new file mode 100644 index 000000000..93524fb0c --- /dev/null +++ b/core/os/os2/env_posix.odin @@ -0,0 +1,77 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if key == "" { + return + } + + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + cval := posix.getenv(ckey) + if cval == nil { + return + } + + found = true + value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? + + return +} + +_set_env :: proc(key, value: string) -> (ok: bool) { + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + cval := strings.clone_to_cstring(key, temp_allocator()) + + ok = posix.setenv(ckey, cval, true) == .OK + return +} + +_unset_env :: proc(key: string) -> (ok: bool) { + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + + ok = posix.unsetenv(ckey) == .OK + return +} + +// NOTE(laytan): clearing the env is weird, why would you ever do that? + +_clear_env :: proc() { + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + key := strings.truncate_to_byte(string(entry), '=') + _unset_env(key) + } +} + +_environ :: proc(allocator: runtime.Allocator) -> (environ: []string) { + n := 0 + for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} + + err: runtime.Allocator_Error + if environ, err = make([]string, n, allocator); err != nil { + // NOTE(laytan): is the environment empty or did allocation fail, how does the user know? + return + } + + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + if environ[i], err = strings.clone(string(entry), allocator); err != nil { + // NOTE(laytan): is the entire environment returned or did allocation fail, how does the user know? + return + } + } + + return +} + + diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 870b5a731..ac30eb1d4 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -20,8 +20,6 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", true } - TEMP_ALLOCATOR_GUARD() - b := make([]u16, n+1, temp_allocator()) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index bc51bb1e8..8a2163634 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -23,13 +23,14 @@ General_Error :: enum u32 { Invalid_Dir, Invalid_Path, Invalid_Callback, + Invalid_Command, Pattern_Has_Separator, Unsupported, } -Platform_Error :: enum i32 {None=0} +Platform_Error :: _Platform_Error Error :: union #shared_nil { General_Error, @@ -69,6 +70,7 @@ error_string :: proc(ferr: Error) -> string { case .Invalid_Dir: return "invalid directory" case .Invalid_Path: return "invalid path" case .Invalid_Callback: return "invalid callback" + case .Invalid_Command: return "invalid command" 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 7f28d1c41..ed55ea15e 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -3,6 +3,8 @@ package os2 import "core:sys/linux" +_Platform_Error :: linux.Errno + @(rodata) _errno_strings := [linux.Errno]string{ .NONE = "", @@ -152,6 +154,14 @@ _get_platform_error :: proc(errno: linux.Errno) -> Error { return .Exist case .ENOENT: return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory } return Platform_Error(i32(errno)) diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin new file mode 100644 index 000000000..9e3424a4a --- /dev/null +++ b/core/os/os2/errors_posix.odin @@ -0,0 +1,32 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" + +_Platform_Error :: posix.Errno + +_error_string :: proc(errno: i32) -> string { + return string(posix.strerror(posix.Errno(errno))) +} + +_get_platform_error :: proc() -> Error { + #partial switch errno := posix.errno(); errno { + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory + case: + return Platform_Error(errno) + } +} diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6421d26ee..8a9a47ca6 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -5,6 +5,8 @@ import "base:runtime" import "core:slice" import win32 "core:sys/windows" +_Platform_Error :: win32.System_Error + _error_string :: proc(errno: i32) -> string { e := win32.DWORD(errno) if e == 0 { @@ -50,6 +52,9 @@ _get_platform_error :: proc() -> Error { case win32.ERROR_INVALID_HANDLE: return .Invalid_File + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, @@ -68,4 +73,4 @@ _get_platform_error :: proc() -> Error { // fallthrough } return Platform_Error(err) -} \ No newline at end of file +} diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 454bc50b9..eedf8570c 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -115,7 +115,11 @@ open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, @(require_results) new_file :: proc(handle: uintptr, name: string) -> ^File { - return _new_file(handle, name) or_else panic("Out of memory") + file, err := _new_file(handle, name) + if err != nil { + panic(error_string(err)) + } + return file } @(require_results) @@ -128,6 +132,12 @@ name :: proc(f: ^File) -> string { return _name(f) } +/* + Close a file and its stream. + + Any further use of the file or its stream should be considered to be in the + same class of bugs as a use-after-free. +*/ close :: proc(f: ^File) -> Error { if f != nil { return io.close(f.stream) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 6b981cca1..ad6ddbf17 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -19,33 +19,18 @@ File_Impl :: struct { } _stdin := File{ - impl = &File_Impl{ - name = "/proc/self/fd/0", - fd = 0, - allocator = file_allocator(), - }, stream = { procedure = _file_stream_proc, }, fstat = _fstat, } _stdout := File{ - impl = &File_Impl{ - name = "/proc/self/fd/1", - fd = 1, - allocator = file_allocator(), - }, stream = { procedure = _file_stream_proc, }, fstat = _fstat, } _stderr := File{ - impl = &File_Impl{ - name = "/proc/self/fd/2", - fd = 2, - allocator = file_allocator(), - }, stream = { procedure = _file_stream_proc, }, @@ -54,10 +39,33 @@ _stderr := File{ @init _standard_stream_init :: proc() { - // cannot define these manually because cyclic reference - _stdin.stream.data = &_stdin - _stdout.stream.data = &_stdout - _stderr.stream.data = &_stderr + @static stdin_impl := File_Impl { + name = "/proc/self/fd/0", + fd = 0, + } + + @static stdout_impl := File_Impl { + name = "/proc/self/fd/1", + fd = 1, + } + + @static stderr_impl := File_Impl { + name = "/proc/self/fd/2", + fd = 2, + } + + stdin_impl.allocator = file_allocator() + stdout_impl.allocator = file_allocator() + stderr_impl.allocator = file_allocator() + + _stdin.impl = &stdin_impl + _stdout.impl = &stdout_impl + _stderr.impl = &stderr_impl + + // cannot define these initially because cyclic reference + _stdin.stream.data = &stdin_impl + _stdout.stream.data = &stdout_impl + _stderr.stream.data = &stderr_impl stdin = &_stdin stdout = &_stdout @@ -72,7 +80,7 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err // terminal would be incredibly rare. This has no effect on files while // allowing us to open serial devices. sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC} - switch flags & O_RDONLY|O_WRONLY|O_RDWR { + switch flags & (O_RDONLY|O_WRONLY|O_RDWR) { case O_RDONLY: case O_WRONLY: sys_flags += {.WRONLY} case O_RDWR: sys_flags += {.RDWR} @@ -162,11 +170,23 @@ _name :: proc(f: ^File) -> string { } _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + // We have to handle this here, because Linux returns EINVAL for both + // invalid offsets and invalid whences. + switch whence { + case .Start, .Current, .End: + break + case: + return 0, .Invalid_Whence + } n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) - if errno != .NONE { + #partial switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case .NONE: + return n, nil + case: return -1, _get_platform_error(errno) } - return n, nil } _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { @@ -181,6 +201,9 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) == 0 { + return 0, nil + } if offset < 0 { return 0, .Invalid_Offset } @@ -206,6 +229,9 @@ _write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) == 0 { + return 0, nil + } if offset < 0 { return 0, .Invalid_Offset } @@ -217,12 +243,18 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { } _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { + // TODO: Identify 0-sized "pseudo" files and return No_Size. This would + // eliminate the need for the _read_entire_pseudo_file procs. s: linux.Stat = --- errno := linux.fstat(f.fd, &s) if errno != .NONE { return -1, _get_platform_error(errno) } - return i64(s.size), nil + + if s.mode & linux.S_IFMT == linux.S_IFREG { + return i64(s.size), nil + } + return 0, .No_Size } _sync :: proc(f: ^File) -> Error { @@ -390,21 +422,15 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { _exists :: proc(name: string) -> bool { TEMP_ALLOCATOR_GUARD() name_cstr, _ := temp_cstring(name) - res, errno := linux.access(name_cstr, linux.F_OK) - return !res && errno == .NONE + return linux.access(name_cstr, linux.F_OK) == .NONE } -/* 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 - * since these "files" stat to a size of 0. Here, we just - * read until there is nothing left. - */ +/* For reading Linux system files that stat to size 0 */ _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { - name_cstr := clone_to_cstring(name, allocator) or_return - defer delete(name, allocator) + TEMP_ALLOCATOR_GUARD() + name_cstr := clone_to_cstring(name, temp_allocator()) or_return return _read_entire_pseudo_file_cstring(name_cstr, allocator) } @@ -434,7 +460,6 @@ _read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Alloc } resize(&contents, i + n) - return contents[:], nil } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin new file mode 100644 index 000000000..dae85d224 --- /dev/null +++ b/core/os/os2/file_posix.odin @@ -0,0 +1,467 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:io" +import "core:c" +import "core:time" +import "core:sys/posix" + +// Most implementations will EINVAL at some point when doing big writes. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + cname: cstring, + fd: posix.FD, +} + +@(init) +init_std_files :: proc() { + // NOTE: is this (paths) also the case on non darwin? + + stdin = __new_file(posix.STDIN_FILENO) + (^File_Impl)(stdin.impl).name = "/dev/stdin" + (^File_Impl)(stdin.impl).cname = "/dev/stdin" + + stdout = __new_file(posix.STDIN_FILENO) + (^File_Impl)(stdout.impl).name = "/dev/stdout" + (^File_Impl)(stdout.impl).cname = "/dev/stdout" + + stderr = __new_file(posix.STDIN_FILENO) + (^File_Impl)(stderr.impl).name = "/dev/stderr" + (^File_Impl)(stderr.impl).cname = "/dev/stderr" +} + +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + sys_flags := posix.O_Flags{.NOCTTY, .CLOEXEC} + + if .Write in flags { + if .Read in flags { + sys_flags += {.RDWR} + } else { + sys_flags += {.WRONLY} + } + } + + 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 .Inheritable in flags { sys_flags -= {.CLOEXEC} } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) + if fd < 0 { + err = _get_platform_error() + return + } + + return _new_file(uintptr(fd), name) +} + +_new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } else if handle == ~uintptr(0) { + err = .Invalid_File + return + } + + crname := _posix_absolute_path(posix.FD(handle), name, file_allocator()) or_return + rname := string(crname) + + f = __new_file(posix.FD(handle)) + impl := (^File_Impl)(f.impl) + impl.name = rname + impl.cname = crname + + return f, nil +} + +__new_file :: proc(handle: posix.FD) -> ^File { + impl := new(File_Impl, file_allocator()) + impl.file.impl = impl + impl.fd = posix.FD(handle) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if f == nil { return nil } + + if posix.close(f.fd) != .OK { + err = _get_platform_error() + } + + delete(f.cname, file_allocator()) + free(f, file_allocator()) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> posix.FD { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + return -1 +} + +_name :: proc(f: ^File) -> string { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).name + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + if posix.fsync(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + if posix.ftruncate(__fd(f), posix.off_t(size)) != .OK { + return _get_platform_error() + } + return nil +} + +_remove :: proc(name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.remove(cname) != 0 { + return _get_platform_error() + } + return nil +} + +_rename :: proc(old_path, new_path: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_path) + cnew := temp_cstring(new_path) + if posix.rename(cold, cnew) != 0 { + return _get_platform_error() + } + return nil +} + +_link :: proc(old_name, new_name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_name) + cnew := temp_cstring(new_name) + if posix.link(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_symlink :: proc(old_name, new_name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_name) + cnew := temp_cstring(new_name) + if posix.symlink(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + buf: [dynamic]byte + buf.allocator = allocator + defer if err != nil { delete(buf) } + + // Loop this because the file might've grown between lstat() and readlink(). + for { + stat: posix.stat_t + if posix.lstat(cname, &stat) != .OK { + err = _get_platform_error() + return + } + + bufsiz := int(stat.st_size + 1 if stat.st_size > 0 else posix.PATH_MAX) + + if bufsiz == len(buf) { + bufsiz *= 2 + } + + // Overflow. + if bufsiz <= 0 { + err = Platform_Error(posix.Errno.E2BIG) + return + } + + resize(&buf, bufsiz) or_return + + size := posix.readlink(cname, raw_data(buf), uint(bufsiz)) + if size < 0 { + err = _get_platform_error() + return + } + + // File has probably grown between lstat() and readlink(). + if size == bufsiz { + continue + } + + s = string(buf[:size]) + return + } +} + +_chdir :: proc(name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chdir(cname) != .OK { + return _get_platform_error() + } + return nil +} + +_fchdir :: proc(f: ^File) -> Error { + if posix.fchdir(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchmod :: proc(f: ^File, mode: int) -> Error { + if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_chmod :: proc(name: string, mode: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + if posix.fchown(__fd(f), posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + times := [2]posix.timeval{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(atime._nsec%1e9/1000), /* microseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(mtime._nsec%1e9/1000), /* microseconds */ + }, + } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + if posix.utimes(cname, ×) != .OK { + return _get_platform_error() + } + return nil +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + times := [2]posix.timespec{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_nsec = c.long(atime._nsec%1e9), /* nanoseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_nsec = c.long(mtime._nsec%1e9), /* nanoseconds */ + }, + } + + if posix.futimens(__fd(f), ×) != .OK { + return _get_platform_error() + } + return nil +} + +_exists :: proc(path: string) -> bool { + TEMP_ALLOCATOR_GUARD() + cpath := temp_cstring(path) + return posix.access(cpath) == .OK +} + +_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_Impl)(stream_data) + fd := f.fd + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := uint(min(len(p), MAX_RW)) + n = i64(posix.read(fd, raw_data(p), to_read)) + switch { + case n == 0: + err = .EOF + case n < 0: + err = .Unknown + } + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := uint(min(len(p), MAX_RW)) + n = i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset))) + switch { + case n == 0: + err = .EOF + case n < 0: + err = .Unknown + } + return + + case .Write: + p := p + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.write(fd, raw_data(p), to_write)); _n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + } + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.pwrite(fd, raw_data(p), to_write, posix.off_t(offset))); _n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + offset += _n + } + } + return + + case .Seek: + #assert(int(posix.Whence.SET) == int(io.Seek_From.Start)) + #assert(int(posix.Whence.CUR) == int(io.Seek_From.Current)) + #assert(int(posix.Whence.END) == int(io.Seek_From.End)) + + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + + n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence))) + if n < 0 { + #partial switch posix.get_errno() { + case .EINVAL: + err = .Invalid_Offset + case: + err = .Unknown + } + } + return + + case .Size: + stat: posix.stat_t + if posix.fstat(fd, &stat) != .OK { + err = .Unknown + return + } + + n = i64(stat.st_size) + return + + case .Flush: + ferr := _sync(&f.file) + 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}) + + case: + return 0, .Empty + } +} diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin new file mode 100644 index 000000000..056d775e6 --- /dev/null +++ b/core/os/os2/file_posix_darwin.odin @@ -0,0 +1,18 @@ +//+private +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 50 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/os2/file_posix_freebsd.odin new file mode 100644 index 000000000..e5007f8fe --- /dev/null +++ b/core/os/os2/file_posix_freebsd.odin @@ -0,0 +1,47 @@ +//+private +package os2 + +import "base:runtime" + +import "core:c" +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: 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 + // my knowledge, there hasn't been any work done on it. + // + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 + // + // This may be unreliable, according to a comment from 2023. + + KInfo_File :: struct { + structsize: c.int, + type: c.int, + fd: c.int, + ref_count: c.int, + flags: c.int, + pad0: c.int, + offset: i64, + + // NOTE(Feoramund): This field represents a complicated union that I am + // avoiding implementing for now. I only need the path data below. + _union: [336]byte, + + path: [posix.PATH_MAX]c.char, + } + + F_KINFO :: 22 + + kinfo: KInfo_File + kinfo.structsize = size_of(KInfo_File) + + res := posix.fcntl(fd, posix.FCNTL_Cmd(F_KINFO), &kinfo) + if res == -1 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&kinfo.path[0])), allocator) +} diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/os2/file_posix_netbsd.odin new file mode 100644 index 000000000..a9cc77a41 --- /dev/null +++ b/core/os/os2/file_posix_netbsd.odin @@ -0,0 +1,18 @@ +//+private +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 15 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin new file mode 100644 index 000000000..929622578 --- /dev/null +++ b/core/os/os2/file_posix_other.odin @@ -0,0 +1,21 @@ +//+private +//+build openbsd +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + buf: [posix.PATH_MAX]byte + path = posix.realpath(cname, raw_data(buf[:])) + if path == nil { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(path), allocator) +} diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index b982afb3e..963544985 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -119,23 +119,18 @@ read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) - @(require_results) read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (data: []byte, err: Error) { size: int - has_size := true + has_size := false if size64, serr := file_size(f); serr == nil { - if i64(int(size64)) != size64 { + if i64(int(size64)) == size64 { + has_size = true size = int(size64) } - } else if serr == .No_Size { - has_size = false - } else { - return } - size += 1 // for EOF - // TODO(bill): Is this correct logic? - if has_size { + if has_size && size > 0 { total: int data = make([]byte, size, allocator) or_return - for { + for total < len(data) { n: int n, err = read(f, data[total:]) total += n @@ -144,18 +139,19 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d err = nil } data = data[:total] - return + break } } + return } else { buffer: [1024]u8 out_buffer := make([dynamic]u8, 0, 0, allocator) total := 0 for { - n: int = --- + n: int n, err = read(f, buffer[:]) total += n - append_elems(&out_buffer, ..buffer[:total]) + append_elems(&out_buffer, ..buffer[:n]) if err != nil { if err == .EOF || err == .Broken_Pipe { err = nil diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 74067464b..382156420 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -248,6 +248,8 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er case .Start: w = win32.FILE_BEGIN case .Current: w = win32.FILE_CURRENT case .End: w = win32.FILE_END + case: + return 0, .Invalid_Whence } hi := i32(offset>>32) lo := i32(offset) @@ -264,6 +266,11 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { } _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + length := len(p) + if length == 0 { + return + } + read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil @@ -318,7 +325,6 @@ _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { single_read_length: win32.DWORD total_read: int - length := len(p) sync.shared_guard(&f.rw_mutex) // multiple readers @@ -337,6 +343,10 @@ _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { if single_read_length > 0 && ok { total_read += int(single_read_length) + } else if single_read_length == 0 && ok { + // ok and 0 bytes means EOF: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + err = .EOF } else { err = _get_platform_error() } @@ -352,7 +362,7 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) buf = buf[:MAX_RW] } - curr_offset := _seek(f, offset, .Current) or_return + curr_offset := _seek(f, 0, .Current) or_return defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ @@ -421,7 +431,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) buf = buf[:MAX_RW] } - curr_offset := _seek(f, offset, .Current) or_return + curr_offset := _seek(f, 0, .Current) or_return defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ @@ -466,13 +476,13 @@ _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { if f != nil && f.impl != nil { - return _flush((^File_Impl)(f.impl)) + return _flush_internal((^File_Impl)(f.impl)) } return nil } _flush :: proc(f: ^File_Impl) -> Error { - return _flush(f) + return _flush_internal(f) } _flush_internal :: proc(f: ^File_Impl) -> Error { handle := _handle(&f.file) @@ -813,7 +823,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, err = error_to_io_error(ferr) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) } return 0, .Empty } diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index 11cf5ab41..e765c320b 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -280,7 +280,8 @@ heap_alloc :: proc(size: int) -> rawptr { _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx) } user_ptr, used := _region_get_block(_local_region, idx, blocks_needed) - _local_region.hdr.free_blocks -= (used + 1) + + sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release) // If this memory was ever used before, it now needs to be zero'd. if idx < _local_region.hdr.last_used { @@ -307,7 +308,7 @@ heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_chec heap_free :: proc(memory: rawptr) { alloc := _get_allocation_header(memory) - if alloc.requested & IS_DIRECT_MMAP == IS_DIRECT_MMAP { + if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP { _direct_mmap_free(alloc) return } @@ -462,25 +463,31 @@ _region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { alloc := alloc add_to_free_list := true - _local_region.hdr.free_blocks += _get_block_count(alloc^) + 1 + idx := sync.atomic_load(&alloc.idx) + prev := sync.atomic_load(&alloc.prev) + next := sync.atomic_load(&alloc.next) + block_count := next - idx - 1 + free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1 + sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release) // try to merge with prev - if alloc.idx > 0 && _local_region.memory[alloc.prev].free_idx != NOT_FREE { - _local_region.memory[alloc.prev].next = alloc.next - _local_region.memory[alloc.next].prev = alloc.prev - alloc = &_local_region.memory[alloc.prev] + if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE { + sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release) + _local_region.memory[next].prev = prev + alloc = &_local_region.memory[prev] add_to_free_list = false } // try to merge with next - if alloc.next < BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { - old_next := alloc.next - alloc.next = _local_region.memory[old_next].next - _local_region.memory[alloc.next].prev = alloc.idx + if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE { + old_next := next + sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release) + + sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release) if add_to_free_list { - _local_region.hdr.free_list[_local_region.memory[old_next].free_idx] = alloc.idx - alloc.free_idx = _local_region.memory[old_next].free_idx + sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release) + sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release) } else { // NOTE: We have aleady merged with prev, and now merged with next. // Now, we are actually going to remove from the free_list. @@ -492,10 +499,11 @@ _region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { // This is the only place where anything is appended to the free list. if add_to_free_list { fl := _local_region.hdr.free_list - alloc.free_idx = _local_region.hdr.free_list_len - fl[alloc.free_idx] = alloc.idx - _local_region.hdr.free_list_len += 1 - if int(_local_region.hdr.free_list_len) == len(fl) { + fl_len := sync.atomic_load(&_local_region.hdr.free_list_len) + sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release) + fl[alloc.free_idx] = idx + sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release) + if int(fl_len + 1) == len(fl) { free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list)) _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true) } @@ -512,8 +520,8 @@ _region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) { _region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) { r: ^Region idx: int - for r = global_regions; r != nil; r = r.hdr.next_region { - if idx == local_idx || idx < back_idx || r.hdr.free_blocks < blocks { + for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { + if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks { idx += 1 continue } @@ -581,7 +589,7 @@ _region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_ _region_get_local_idx :: proc() -> int { idx: int - for r := global_regions; r != nil; r = r.hdr.next_region { + for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { if r == _local_region { return idx } @@ -597,9 +605,10 @@ _region_find_and_assign_local :: proc(alloc: ^Allocation_Header) { _local_region = _region_retrieve_from_addr(alloc) } - // At this point, _local_region is set correctly. Spin until acquired - res: ^^Region - for res != &_local_region { + // At this point, _local_region is set correctly. Spin until acquire + res := CURRENTLY_ACTIVE + + for res == CURRENTLY_ACTIVE { res = sync.atomic_compare_exchange_strong_explicit( &_local_region.hdr.local_addr, &_local_region, @@ -621,9 +630,9 @@ _region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_chec _region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check { // pop, swap and update allocation hdr if n := region.hdr.free_list_len - 1; free_idx != n { - region.hdr.free_list[free_idx] = region.hdr.free_list[n] + region.hdr.free_list[free_idx] = sync.atomic_load(®ion.hdr.free_list[n]) alloc_idx := region.hdr.free_list[free_idx] - region.memory[alloc_idx].free_idx = free_idx + sync.atomic_store_explicit(®ion.memory[alloc_idx].free_idx, free_idx, .Release) } region.hdr.free_list_len -= 1 } @@ -714,3 +723,4 @@ _get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Hea _round_up_to_nearest :: #force_inline proc(size, round: int) -> int { return (size-1) + round - (size-1) % round } + diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin new file mode 100644 index 000000000..fcae267fa --- /dev/null +++ b/core/os/os2/heap_posix.odin @@ -0,0 +1,7 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.heap_allocator_proc diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index f7a38f3f1..041cd531b 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -43,7 +43,7 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri } @(require_results) -temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) { +temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error { return clone_to_cstring(s, temp_allocator()) } @@ -76,7 +76,7 @@ concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: st for s in strings { n += len(s) } - buf := make([]byte, n) or_return + buf := make([]byte, n, allocator) or_return n = 0 for s in strings { n += copy(buf[n:], s) @@ -126,5 +126,3 @@ 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 3bf422ccb..254950d68 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -13,13 +13,13 @@ is_path_separator :: proc(c: byte) -> bool { mkdir :: make_directory -make_directory :: proc(name: string, perm: int) -> Error { +make_directory :: proc(name: string, perm: int = 0o755) -> Error { return _mkdir(name, perm) } mkdir_all :: make_directory_all -make_directory_all :: proc(path: string, perm: int) -> Error { +make_directory_all :: proc(path: string, perm: int = 0o755) -> Error { return _mkdir_all(path, perm) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index be60f9b86..ba2f7235c 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,6 +1,7 @@ //+private package os2 +import "core:strings" import "core:strconv" import "base:runtime" import "core:sys/linux" @@ -75,14 +76,6 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return nil if has_created else .Exist } -dirent64 :: struct { - d_ino: u64, - d_off: u64, - d_reclen: u16, - d_type: u8, - d_name: [1]u8, -} - _remove_all :: proc(path: string) -> Error { DT_DIR :: 4 @@ -105,26 +98,18 @@ _remove_all :: proc(path: string) -> Error { return _get_platform_error(errno) } - d: ^dirent64 + offset: int + for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { + d_name_str := linux.dirent_name(d) + d_name_cstr := strings.unsafe_string_to_cstring(d_name_str) - for i := 0; i < buflen; i += int(d.d_reclen) { - d = (^dirent64)(rawptr(&buf[i])) - d_name_cstr := cstring(&d.d_name[0]) - - buf_len := uintptr(d.d_reclen) - offset_of(d.d_name) - - /* check for current directory (.) */ - #no_bounds_check if buf_len > 1 && d.d_name[0] == '.' && d.d_name[1] == 0 { + /* check for current or parent directory (. or ..) */ + if d_name_str == "." || d_name_str == ".." { continue } - /* check for parent directory (..) */ - #no_bounds_check if buf_len > 2 && d.d_name[0] == '.' && d.d_name[1] == '.' && d.d_name[2] == 0 { - continue - } - - switch d.d_type { - case DT_DIR: + #partial switch d.type { + case .DIR: new_dfd: linux.Fd new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS) if errno != .NONE { diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin new file mode 100644 index 000000000..0b9a52532 --- /dev/null +++ b/core/os/os2/path_posix.odin @@ -0,0 +1,126 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" +import "core:path/filepath" + +import "core:sys/posix" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { + return _get_platform_error() + } + return nil +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + TEMP_ALLOCATOR_GUARD() + + if exists(path) { + return .Exist + } + + clean_path := filepath.clean(path, temp_allocator()) + return internal_mkdir_all(clean_path, perm) + + internal_mkdir_all :: proc(path: string, perm: int) -> Error { + dir, file := filepath.split(path) + if file != path { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] + } + internal_mkdir_all(dir, perm) or_return + } + + err := _mkdir(path, perm) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cpath := temp_cstring(path) + + dir := posix.opendir(cpath) + if dir == nil { + return _get_platform_error() + } + defer posix.closedir(dir) + + for { + posix.set_errno(.NONE) + entry := posix.readdir(dir) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + return _get_platform_error() + } else { + break + } + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + + fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) + if entry.d_type == .DIR { + _remove_all(fullpath[:len(fullpath)-1]) + } else { + if posix.unlink(cstring(raw_data(fullpath))) != .OK { + return _get_platform_error() + } + } + } + + if posix.rmdir(cpath) != .OK { + return _get_platform_error() + } + return nil +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf: [dynamic]byte + buf.allocator = temp_allocator() + size := uint(posix.PATH_MAX) + + cwd: cstring + for ; cwd == nil; size *= 2 { + resize(&buf, size) + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if cwd == nil && posix.errno() != .ERANGE { + err = _get_platform_error() + return + } + } + + return clone_string(string(cwd), allocator) +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + cdir := temp_cstring(dir) + if posix.chdir(cdir) != .OK { + err = _get_platform_error() + } + return +} diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index c3fecfb9e..42315cf4e 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, {}) + errno := linux.pipe2(&fds, {.CLOEXEC}) if errno != .NONE { return nil, nil,_get_platform_error(errno) } r = _new_file(uintptr(fds[0])) or_return w = _new_file(uintptr(fds[1])) or_return + return } - diff --git a/core/os/os2/pipe_posix.odin b/core/os/os2/pipe_posix.odin new file mode 100644 index 000000000..13c1f8aec --- /dev/null +++ b/core/os/os2/pipe_posix.odin @@ -0,0 +1,46 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" +import "core:strings" + +_pipe :: proc() -> (r, w: ^File, err: Error) { + fds: [2]posix.FD + if posix.pipe(&fds) != .OK { + err = _get_platform_error() + return + } + + if posix.fcntl(fds[0], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + if posix.fcntl(fds[1], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + + r = __new_file(fds[0]) + ri := (^File_Impl)(r.impl) + + rname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&rname, "/dev/fd/") + strings.write_int(&rname, int(fds[0])) + ri.name = strings.to_string(rname) + ri.cname = strings.to_cstring(&rname) + + w = __new_file(fds[1]) + wi := (^File_Impl)(w.impl) + + wname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&wname, "/dev/fd/") + strings.write_int(&wname, int(fds[1])) + wi.name = strings.to_string(wname) + wi.cname = strings.to_cstring(&wname) + + return +} + diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 3f3e64668..ce65987b0 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -14,7 +14,7 @@ TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration wi */ args := get_args() -@(private="file", require_results) +@(private="file") get_args :: proc() -> []string { result := make([]string, len(runtime.args__), heap_allocator()) for rt_arg, i in runtime.args__ { @@ -131,6 +131,8 @@ Process_Info_Field :: enum { Working_Dir, } +ALL_INFO :: Process_Info_Fields{.Executable_Path, .PPid, .Priority, .Command_Line, .Command_Args, .Environment, .Username, .Working_Dir} + /* Contains information about the process as obtained by the `process_info()` procedure. @@ -164,15 +166,15 @@ Process_Info :: struct { 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. + + Use `free_process_info` to free the memory allocated by this procedure. The + `free_process_info` procedure needs to be called, even if this procedure + returned an error, as some of the fields may have been allocated. **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. + returned by this procedure. */ @(require_results) process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -186,14 +188,14 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: 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. + Use `free_process_info` to free the memory allocated by this procedure. The + `free_process_info` procedure needs to be called, even if this procedure + returned an error, as some of the fields may have been allocated. **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. + returned by this procedure. */ @(require_results) process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -206,14 +208,14 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, 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. + Use `free_process_info` to free the memory allocated by this procedure. The + `free_process_info` procedure needs to be called, even if this procedure + returned an error, as some of the fields may have been allocated. **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. + returned by this procedure. */ @(require_results) current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -239,12 +241,16 @@ process_info :: proc { free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { delete(pi.executable_path, allocator) delete(pi.command_line, allocator) + for a in pi.command_args { + delete(a, allocator) + } delete(pi.command_args, allocator) for s in pi.environment { delete(s, allocator) } delete(pi.environment, allocator) delete(pi.working_dir, allocator) + delete(pi.username, allocator) } /* @@ -299,6 +305,7 @@ Process_Desc :: struct { // 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. + // NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env. env: []string, // 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' diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index d832083b6..b6db46423 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -1,68 +1,381 @@ +//+build linux //+private file package os2 import "base:runtime" +import "base:intrinsics" + import "core:time" +import "core:slice" +import "core:strings" +import "core:strconv" import "core:sys/linux" +import "core:path/filepath" + +PIDFD_UNASSIGNED :: ~uintptr(0) @(private="package") _exit :: proc "contextless" (code: int) -> ! { - linux.exit(i32(code)) + linux.exit_group(i32(code)) } - @(private="package") _get_uid :: proc() -> int { - return -1 + return int(linux.getuid()) } @(private="package") _get_euid :: proc() -> int { - return -1 + return int(linux.geteuid()) } @(private="package") _get_gid :: proc() -> int { - return -1 + return int(linux.getgid()) } @(private="package") _get_egid :: proc() -> int { - return -1 + return int(linux.getegid()) } @(private="package") _get_pid :: proc() -> int { - return -1 + return int(linux.getpid()) } @(private="package") _get_ppid :: proc() -> int { - return -1 + return int(linux.getppid()) } @(private="package") _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + TEMP_ALLOCATOR_GUARD() + + dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) + #partial switch errno { + case .NONE: + // okay + case .ENOTDIR: + err = .Invalid_Dir + return + case .ENOENT: + err = .Not_Exist + return + case: + err = _get_platform_error(errno) + return + } + defer linux.close(dir_fd) + + dynamic_list := make([dynamic]int, temp_allocator()) or_return + + buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return + loop: for { + buflen: int + buflen, errno = linux.getdents(dir_fd, buf[:]) + #partial switch errno { + case .EINVAL: + resize(&buf, len(buf) * 2) + continue loop + case .NONE: + if buflen == 0 { break loop } + case: + return {}, _get_platform_error(errno) + } + + offset: int + for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { + d_name_str := linux.dirent_name(d) + + if pid, ok := strconv.parse_int(d_name_str); ok { + append(&dynamic_list, pid) + } + } + } + + list, err = slice.clone(dynamic_list[:], allocator) return } @(private="package") _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + TEMP_ALLOCATOR_GUARD() + + info.pid = pid + + // Use this to make cstrings without copying. + path_backing: [48]u8 + path_builder := strings.builder_from_bytes(path_backing[:]) + + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + proc_fd, errno := linux.open(strings.to_cstring(&path_builder), _OPENDIR_FLAGS) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + defer linux.close(proc_fd) + + username_if: if .Username in selection { + s: linux.Stat + if errno = linux.fstat(proc_fd, &s); errno != .NONE { + err = _get_platform_error(errno) + break username_if + } + + passwd_bytes: []u8 + passwd_err: Error + passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator()) + if passwd_err != nil { + err = passwd_err + break username_if + } + + passwd := string(passwd_bytes) + for len(passwd) > 0 { + n := strings.index_byte(passwd, ':') + if n < 0 { + break + } + username := passwd[:n] + passwd = passwd[n+1:] + + // skip password field + passwd = passwd[strings.index_byte(passwd, ':') + 1:] + + n = strings.index_byte(passwd, ':') + if uid, ok := strconv.parse_int(passwd[:n]); ok && uid == int(s.uid) { + info.username = strings.clone(username, allocator) or_return + info.fields += {.Username} + break + } else if !ok { + err = .Invalid_File + break username_if + } + + eol := strings.index_byte(passwd, '\n') + if eol < 0 { + break + } + passwd = passwd[eol + 1:] + } + } + + cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/cmdline") + + cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()) + if cmdline_err != nil || len(cmdline_bytes) == 0 { + err = cmdline_err + break cmdline_if + } + cmdline := string(cmdline_bytes) + + terminator := strings.index_byte(cmdline, 0) + assert(terminator > 0) + + command_line_exec := cmdline[:terminator] + + // Still need cwd if the execution on the command line is relative. + cwd: string + cwd_err: Error + if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/cwd") + + cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder), temp_allocator()) // allowed to fail + if cwd_err == nil && .Working_Dir in selection { + info.working_dir = strings.clone(cwd, allocator) or_return + info.fields += {.Working_Dir} + } else if cwd_err != nil { + err = cwd_err + break cmdline_if + } + } + + if .Executable_Path in selection { + if cmdline[0] == '/' { + info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return + info.fields += {.Executable_Path} + } else if cwd_err == nil { + info.executable_path = filepath.join({ cwd, cmdline[:terminator] }, allocator) or_return + info.fields += {.Executable_Path} + } else { + break cmdline_if + } + } + + if selection & {.Command_Line, .Command_Args} != {} { + // skip to first arg + //cmdline = cmdline[terminator + 1:] + command_line_builder: strings.Builder + command_args_list: [dynamic]string + + if .Command_Line in selection { + command_line_builder = strings.builder_make(allocator) or_return + info.fields += {.Command_Line} + } + + for i := 0; len(cmdline) > 0; i += 1 { + if terminator = strings.index_byte(cmdline, 0); terminator < 0 { + break + } + + if .Command_Line in selection { + if i > 0 { + strings.write_byte(&command_line_builder, ' ') + } + strings.write_string(&command_line_builder, cmdline[:terminator]) + } + if .Command_Args in selection { + if i == 1 { + command_args_list = make([dynamic]string, allocator) or_return + info.fields += {.Command_Args} + } + if i > 0 { + arg := strings.clone(cmdline[:terminator], allocator) or_return + append(&command_args_list, arg) or_return + } + } + + cmdline = cmdline[terminator + 1:] + } + info.command_line = strings.to_string(command_line_builder) + info.command_args = command_args_list[:] + } + } + + stat_if: if selection & {.PPid, .Priority} != {} { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/stat") + + proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()) + if stat_err != nil { + err = stat_err + break stat_if + } + if len(proc_stat_bytes) <= 0 { + break stat_if + } + + // Skip to the first field after the executable name + stats: string + if start := strings.last_index_byte(string(proc_stat_bytes), ')'); start != -1 { + stats = string(proc_stat_bytes[start + 2:]) + } else { + break stat_if + } + + // NOTE: index 0 corresponds to field 3 (state) from `man 5 proc_pid_stat` + // because we skipped passed the executable name above. + Fields :: enum { + State, + PPid, + PGrp, + Session, + Tty_Nr, + TpGid, + Flags, + MinFlt, + CMinFlt, + MajFlt, + CMajFlt, + UTime, + STime, + CUTime, + CSTime, + Priority, + Nice, + //... etc, + } + stat_fields := strings.split(stats, " ", temp_allocator()) or_return + + if len(stat_fields) <= int(Fields.Nice) { + break stat_if + } + + if .PPid in selection { + if ppid, ok := strconv.parse_int(stat_fields[Fields.PPid]); ok { + info.ppid = ppid + info.fields += {.PPid} + } else { + err = .Invalid_File + break stat_if + } + } + + if .Priority in selection { + if nice, ok := strconv.parse_int(stat_fields[Fields.Nice]); ok { + info.priority = nice + info.fields += {.Priority} + } else { + err = .Invalid_File + break stat_if + } + } + } + + if .Environment in selection { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/environ") + + if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()); env_err == nil { + env := string(env_bytes) + + env_list := make([dynamic]string, allocator) or_return + for len(env) > 0 { + terminator := strings.index_byte(env, 0) + if terminator <= 0 { + break + } + e := strings.clone(env[:terminator], allocator) or_return + append(&env_list, e) or_return + env = env[terminator + 1:] + } + info.environment = env_list[:] + info.fields += {.Environment} + } else if err == nil { + err = env_err + } + } + return } @(private="package") _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - return + return _process_info_by_pid(process.pid, selection, allocator) } @(private="package") _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - return + return _process_info_by_pid(get_pid(), selection, allocator) } @(private="package") -_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { +_process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + process.handle = PIDFD_UNASSIGNED + + pidfd, errno := linux.pidfd_open(linux.Pid(pid), {}) + if errno == .ENOSYS { + return process, .Unsupported + } + if errno != .NONE { + return process, _get_platform_error(errno) + } + process.handle = uintptr(pidfd) return } @@ -71,25 +384,483 @@ _Sys_Process_Attributes :: struct {} @(private="package") _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + has_executable_permissions :: proc(fd: linux.Fd) -> bool { + backing: [48]u8 + b := strings.builder_from_bytes(backing[:]) + strings.write_string(&b, "/proc/self/fd/") + strings.write_int(&b, int(fd)) + return linux.access(strings.to_cstring(&b), linux.X_OK) == .NONE + } + + TEMP_ALLOCATOR_GUARD() + + if len(desc.command) == 0 { + return process, .Invalid_Command + } + + dir_fd := linux.AT_FDCWD + errno: linux.Errno + if desc.working_dir != "" { + dir_cstr := temp_cstring(desc.working_dir) or_return + if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { + return process, _get_platform_error(errno) + } + } + defer if desc.working_dir != "" { + linux.close(dir_fd) + } + + // search PATH if just a plain name is provided + exe_fd: linux.Fd + executable_name := desc.command[0] + if strings.index_byte(executable_name, '/') < 0 { + path_env := get_env("PATH", temp_allocator()) + path_dirs := filepath.split_list(path_env, temp_allocator()) or_return + + exe_builder := strings.builder_make(temp_allocator()) or_return + + found: bool + for dir in path_dirs { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, dir) + strings.write_byte(&exe_builder, '/') + strings.write_string(&exe_builder, executable_name) + + exe_path := strings.to_cstring(&exe_builder) + if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { + continue + } + if !has_executable_permissions(exe_fd) { + linux.close(exe_fd) + continue + } + found = true + break + } + if !found { + // check in cwd to match windows behavior + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, "./") + strings.write_string(&exe_builder, executable_name) + + exe_path := strings.to_cstring(&exe_builder) + if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { + return process, .Not_Exist + } + if !has_executable_permissions(exe_fd) { + linux.close(exe_fd) + return process, .Permission_Denied + } + } + } else { + exe_path := temp_cstring(executable_name) or_return + if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { + return process, _get_platform_error(errno) + } + if !has_executable_permissions(exe_fd) { + linux.close(exe_fd) + return process, .Permission_Denied + } + } + + // At this point, we have an executable. + defer linux.close(exe_fd) + + // args and environment need to be a list of cstrings + // that are terminated by a nil pointer. + cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return + for command, i in desc.command { + cargs[i] = temp_cstring(command) or_return + } + + // Use current process' environment if description didn't provide it. + env: [^]cstring + if desc.env == nil { + // take this process's current environment + env = raw_data(export_cstring_environment(temp_allocator())) + } else { + cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return + for env, i in desc.env { + cenv[i] = temp_cstring(env) or_return + } + env = &cenv[0] + } + + child_pipe_fds: [2]linux.Fd + if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE { + return process, _get_platform_error(errno) + } + defer linux.close(child_pipe_fds[READ]) + + + // TODO: This is the traditional textbook implementation with fork. + // A more efficient implementation with vfork: + // + // 1. retrieve signal handlers + // 2. block all signals + // 3. allocate some stack space + // 4. vfork (waits for child exit or execve); In child: + // a. set child signal handlers + // b. set up any necessary pipes + // c. execve + // 5. restore signal handlers + // + pid: linux.Pid + if pid, errno = linux.fork(); errno != .NONE { + linux.close(child_pipe_fds[WRITE]) + return process, _get_platform_error(errno) + } + + STDIN :: linux.Fd(0) + STDOUT :: linux.Fd(1) + STDERR :: linux.Fd(2) + + READ :: 0 + WRITE :: 1 + + if pid == 0 { + // in child process now + write_errno_to_parent_and_abort :: proc(parent_fd: linux.Fd, errno: linux.Errno) -> ! { + error_byte: [1]u8 = { u8(errno) } + linux.write(parent_fd, error_byte[:]) + intrinsics.trap() + } + + stdin_fd: linux.Fd + stdout_fd: linux.Fd + stderr_fd: linux.Fd + + if desc.stdin != nil { + stdin_fd = linux.Fd(fd(desc.stdin)) + } else { + stdin_fd, errno = linux.open("/dev/null", {}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } + + write_devnull: linux.Fd = -1 + + if desc.stdout != nil { + stdout_fd = linux.Fd(fd(desc.stdout)) + } else { + write_devnull, errno = linux.open("/dev/null", {.WRONLY}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + stdout_fd = write_devnull + } + + if desc.stderr != nil { + stderr_fd = linux.Fd(fd(desc.stderr)) + } else { + if write_devnull < 0 { + write_devnull, errno = linux.open("/dev/null", {.WRONLY}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } + stderr_fd = write_devnull + } + + if _, errno = linux.dup2(stdin_fd, STDIN); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + if _, errno = linux.dup2(stdout_fd, STDOUT); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + + errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH}) + assert(errno != nil) + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + + linux.close(child_pipe_fds[WRITE]) + + process.pid = int(pid) + + child_byte: [1]u8 + errno = .EINTR + for errno == .EINTR { + _, errno = linux.read(child_pipe_fds[READ], child_byte[:]) + } + + // If the read failed, something weird happened. Do not return the read + // error so the user knows to wait on it. + if errno == .NONE { + child_errno := linux.Errno(child_byte[0]) + if child_errno != .NONE { + // We can assume it trapped here. + _reap_terminated(process) + process.pid = 0 + return process, _get_platform_error(child_errno) + } + } + + process, _ = process_open(int(pid)) return } -@(private="package") -_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { +_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + + stat_path_buf: [48]u8 + path_builder := strings.builder_from_bytes(stat_path_buf[:]) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, int(state.pid)) + strings.write_string(&path_builder, "/stat") + + stat_buf: []u8 + stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()) + if err != nil { + return + } + + // ')' will be the end of the executable name (item 2) + idx := strings.last_index_byte(string(stat_buf), ')') + stats := string(stat_buf[idx + 2:]) + + // utime and stime are the 14 and 15th items, respectively, and we are + // currently on item 3. Skip 11 items here. + for _ in 0..<11 { + stats = stats[strings.index_byte(stats, ' ') + 1:] + } + + idx = strings.index_byte(stats, ' ') + utime_str := stats[:idx] + + stats = stats[idx + 1:] + stime_str := stats[:strings.index_byte(stats, ' ')] + + utime, stime: int + ok: bool + if utime, ok = strconv.parse_int(utime_str, 10); !ok { + return .Invalid_File + } + if stime, ok = strconv.parse_int(stime_str, 10); !ok { + return .Invalid_File + } + + // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms + state.user_time = time.Duration(utime) * 10 * time.Millisecond + state.system_time = time.Duration(stime) * 10 * time.Millisecond + return } +_reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error) { + state.pid = process.pid + _process_state_update_times(&state) + + info: linux.Sig_Info + errno := linux.Errno.EINTR + for errno == .EINTR { + errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED}, nil) + } + err = _get_platform_error(errno) + + switch linux.Sig_Child_Code(info.code) { + case .NONE, .CONTINUED, .STOPPED: + unreachable() + case .EXITED: + state.exited = true + state.exit_code = int(info.status) + state.success = state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + state.exited = true + state.exit_code = int(info.status) + state.success = false + } + return +} + +_timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + timeout := timeout + + process_state.pid = process.pid + pidfd := linux.Fd(process.handle) + pollfd: [1]linux.Poll_Fd = { + { + fd = pidfd, + events = {.IN}, + }, + } + + start_tick := time.tick_now() + + mask: bit_set[0..<64; u64] + mask += { int(linux.Signal.SIGCHLD) - 1 } + sigchld_set := transmute(linux.Sig_Set)(mask) + + info: linux.Sig_Info + for { + if timeout <= 0 { + _process_state_update_times(&process_state) + err = .Timeout + return + } + + ts: linux.Time_Spec = { + time_sec = uint(timeout / time.Second), + time_nsec = uint(timeout % time.Second), + } + + n, errno := linux.ppoll(pollfd[:], &ts, &sigchld_set) + if errno != .NONE { + if errno == .EINTR { + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + continue + } + return process_state, _get_platform_error(errno) + } + + if n == 0 { // timeout with no events + _process_state_update_times(&process_state) + err = .Timeout + return + } + + if errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE { + return process_state, _get_platform_error(errno) + } + + if info.signo == .SIGCHLD { + break + } + + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + } + + // _reap_terminated for pidfd + { + _process_state_update_times(&process_state) + + errno := linux.Errno.EINTR + for errno == .EINTR { + errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED}, nil) + } + err = _get_platform_error(errno) + + switch linux.Sig_Child_Code(info.code) { + case .NONE, .CONTINUED, .STOPPED: + unreachable() + case .EXITED: + process_state.exited = true + process_state.exit_code = int(info.status) + process_state.success = process_state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + process_state.exited = true + process_state.exit_code = int(info.status) + process_state.success = false + } + } + return +} + +_timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + timeout := timeout + process_state.pid = process.pid + + mask: bit_set[0..<64; u64] + mask += { int(linux.Signal.SIGCHLD) - 1 } + sigchld_set := transmute(linux.Sig_Set)(mask) + + start_tick := time.tick_now() + + org_sigset: linux.Sig_Set + errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset) + if errno != .NONE { + return process_state, _get_platform_error(errno) + } + defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil) + + // In case there was a signal handler on SIGCHLD, avoid race + // condition by checking wait first. + info: linux.Sig_Info + errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WNOWAIT, .WEXITED, .WNOHANG}, nil) + + for errno != .NONE || info.code == 0 || info.pid != linux.Pid(process.pid) { + if timeout <= 0 { + _process_state_update_times(&process_state) + err = .Timeout + return + } + + ts: linux.Time_Spec = { + time_sec = uint(timeout / time.Second), + time_nsec = uint(timeout % time.Second), + } + + _, errno = linux.rt_sigtimedwait(&sigchld_set, &info, &ts) + #partial switch errno { + case .EAGAIN: // timeout + _process_state_update_times(&process_state) + err = .Timeout + return + case .EINTR: + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + case .EINVAL: + return process_state, _get_platform_error(errno) + } + } + + return _reap_terminated(process) +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) { + if timeout > 0 { + if process.handle == PIDFD_UNASSIGNED { + return _timed_wait_on_pid(process, timeout) + } else { + return _timed_wait_on_handle(process, timeout) + } + } + + process_state: Process_State = { + pid = process.pid, + } + + errno: linux.Errno + options: linux.Wait_Options = {.WEXITED} + if timeout == 0 { + options += {.WNOHANG} + } + + info: linux.Sig_Info + + errno = .EINTR + for errno == .EINTR { + errno = linux.waitid(.PID, linux.Id(process.pid), &info, options + {.WNOWAIT}, nil) + } + if errno == .EAGAIN || (errno == .NONE && info.signo != .SIGCHLD) { + _process_state_update_times(&process_state) + return process_state, .Timeout + } + if errno != .NONE { + return process_state, _get_platform_error(errno) + } + + return _reap_terminated(process) +} + @(private="package") _process_close :: proc(process: Process) -> Error { - return nil + if process.handle == 0 || process.handle == PIDFD_UNASSIGNED { + return nil + } + pidfd := linux.Fd(process.handle) + return _get_platform_error(linux.close(pidfd)) } @(private="package") _process_kill :: proc(process: Process) -> Error { - return nil + return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGKILL)) } -@(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_posix.odin b/core/os/os2/process_posix.odin new file mode 100644 index 000000000..ea4ada81e --- /dev/null +++ b/core/os/os2/process_posix.odin @@ -0,0 +1,352 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:time" +import "core:strings" +import "core:path/filepath" + +import kq "core:sys/kqueue" +import "core:sys/posix" + +_exit :: proc "contextless" (code: int) -> ! { + posix.exit(i32(code)) +} + +_get_uid :: proc() -> int { + return int(posix.getuid()) +} + +_get_euid :: proc() -> int { + return int(posix.geteuid()) +} + +_get_gid :: proc() -> int { + return int(posix.getgid()) +} + +_get_egid :: proc() -> int { + return int(posix.getegid()) +} + +_get_pid :: proc() -> int { + return int(posix.getpid()) +} + +_get_ppid :: proc() -> int { + return int(posix.getppid()) +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(process.pid, selection, allocator) +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(_get_pid(), selection, allocator) +} + +_Sys_Process_Attributes :: struct {} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + if len(desc.command) == 0 { + err = .Invalid_Path + return + } + + TEMP_ALLOCATOR_GUARD() + + // search PATH if just a plain name is provided. + exe_builder := strings.builder_make(temp_allocator()) + exe_name := desc.command[0] + if strings.index_byte(exe_name, '/') < 0 { + path_env := get_env("PATH", temp_allocator()) + path_dirs := filepath.split_list(path_env, temp_allocator()) + + found: bool + for dir in path_dirs { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, dir) + strings.write_byte(&exe_builder, '/') + strings.write_string(&exe_builder, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + continue + } else { + posix.close(exe_fd) + found = true + break + } + } + if !found { + // check in cwd to match windows behavior + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, desc.working_dir) + if len(desc.working_dir) > 0 && desc.working_dir[len(desc.working_dir)-1] != '/' { + strings.write_byte(&exe_builder, '/') + } + strings.write_string(&exe_builder, "./") + strings.write_string(&exe_builder, exe_name) + + // "hello/./world" is fine right? + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + } else { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + + cwd: cstring; if desc.working_dir != "" { + cwd = temp_cstring(desc.working_dir) + } + + cmd := make([]cstring, len(desc.command) + 1, temp_allocator()) + for part, i in desc.command { + cmd[i] = temp_cstring(part) + } + + env: [^]cstring + if desc.env == nil { + // take this process's current environment + env = posix.environ + } else { + cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) + for env, i in desc.env { + cenv[i] = temp_cstring(env) + } + env = raw_data(cenv) + } + + READ :: 0 + WRITE :: 1 + + pipe: [2]posix.FD + if posix.pipe(&pipe) != .OK { + err = _get_platform_error() + return + } + defer posix.close(pipe[READ]) + + if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + } + if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + } + + switch pid := posix.fork(); pid { + case -1: + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + + case 0: + abort :: proc(parent_fd: posix.FD) -> ! { + #assert(len(posix.Errno) < max(u8)) + errno := u8(posix.errno()) + posix.write(parent_fd, &errno, 1) + runtime.trap() + } + + null := posix.open("/dev/null", {.RDWR}) + if null == -1 { abort(pipe[WRITE]) } + + stderr := (^File_Impl)(desc.stderr.impl).fd if desc.stderr != nil else null + stdout := (^File_Impl)(desc.stdout.impl).fd if desc.stdout != nil else null + stdin := (^File_Impl)(desc.stdin.impl).fd if desc.stdin != nil else null + + if posix.dup2(stderr, posix.STDERR_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdout, posix.STDOUT_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdin, posix.STDIN_FILENO ) == -1 { abort(pipe[WRITE]) } + + if cwd != nil { + if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) } + } + + res := posix.execve(strings.to_cstring(&exe_builder), raw_data(cmd), env) + assert(res == -1) + abort(pipe[WRITE]) + + case: + posix.close(pipe[WRITE]) + + errno: posix.Errno + for { + errno_byte: u8 + switch posix.read(pipe[READ], &errno_byte, 1) { + case 1: + errno = posix.Errno(errno_byte) + case -1: + errno = posix.errno() + if errno == .EINTR { + continue + } else { + // If the read failed, something weird happened. Do not return the read + // error so the user knows to wait on it. + errno = nil + } + } + break + } + + if errno != nil { + // We can assume it trapped here. + + for { + info: posix.siginfo_t + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 && posix.errno() == .EINTR { + continue + } + break + } + + err = errno + return + } + + process.pid = int(pid) + process, _ = _process_open(int(pid), {}) + return + } +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + process_state.pid = process.pid + + _process_handle_still_valid(process) or_return + + // timeout > 0 = use kqueue to wait (with a timeout) on process exit + // timeout == 0 = use waitid with WNOHANG so it returns immediately + // timeout > 0 = use waitid without WNOHANG so it waits indefinitely + // + // at the end use waitid to actually reap the process and get it's status + + if timeout > 0 { + timeout := timeout + + queue := kq.kqueue() or_return + defer posix.close(queue) + + changelist, eventlist: [1]kq.KEvent + + changelist[0] = { + ident = uintptr(process.pid), + filter = .Proc, + flags = { .Add }, + fflags = { + fproc = { .Exit }, + }, + } + + for { + start := time.tick_now() + n, kerr := kq.kevent(queue, changelist[:], eventlist[:], &{ + tv_sec = posix.time_t(timeout / time.Second), + tv_nsec = i64(timeout % time.Second), + }) + if kerr == .EINTR { + timeout -= time.tick_since(start) + continue + } else if kerr != nil { + err = kerr + return + } else if n == 0 { + err = .Timeout + _process_state_update_times(process, &process_state) + return + } else { + _process_state_update_times(process, &process_state) + break + } + } + } else { + flags := posix.Wait_Flags{.EXITED, .NOWAIT} + if timeout == 0 { + flags += {.NOHANG} + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, flags) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + _process_state_update_times(process, &process_state) + + if info.si_signo == nil { + assert(timeout == 0) + err = .Timeout + return + } + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + switch info.si_code.chld { + case: unreachable() + case .CONTINUED, .STOPPED: unreachable() + case .EXITED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = process_state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = false + } + + return +} + +_process_close :: proc(process: Process) -> Error { + return nil +} + +_process_kill :: proc(process: Process) -> (err: Error) { + _process_handle_still_valid(process) or_return + + if posix.kill(posix.pid_t(process.pid), .SIGKILL) != .OK { + err = _get_platform_error() + } + + return +} diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin new file mode 100644 index 000000000..648c4d389 --- /dev/null +++ b/core/os/os2/process_posix_darwin.odin @@ -0,0 +1,304 @@ +//+private +package os2 + +import "base:runtime" +import "base:intrinsics" + +import "core:bytes" +import "core:sys/darwin" +import "core:sys/posix" +import "core:sys/unix" +import "core:time" + +foreign import lib "system:System.framework" + +foreign lib { + sysctl :: proc( + name: [^]i32, namelen: u32, + oldp: rawptr, oldlenp: ^uint, + newp: rawptr, newlen: uint, + ) -> posix.result --- +} + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, prio: Maybe(i32), uid: posix.uid_t, ok: bool) { + // Short info is enough and requires less permissions if the priority isn't requested. + if .Priority in selection { + info: darwin.proc_taskallinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .TASKALLINFO, 0, &info, size_of(info)) + if ret > 0 { + assert(ret == size_of(info)) + ppid = info.pbsd.pbi_ppid + prio = info.ptinfo.pti_priority + uid = info.pbsd.pbi_uid + ok = true + return + } + } + + // Try short info, requires less permissions, but doesn't give a `nice`. + psinfo: darwin.proc_bsdshortinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .SHORTBSDINFO, 0, &psinfo, size_of(psinfo)) + if ret > 0 { + assert(ret == size_of(psinfo)) + ppid = psinfo.pbsi_ppid + uid = psinfo.pbsi_uid + ok = true + } + + return + } + + + info.pid = pid + + // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), + // other errors usually mean other parts of the info could be retrieved though, so in those cases we keep trying to get the other information. + + pidinfo: { + if selection & {.PPid, .Priority, .Username } != {} { + ppid, mprio, uid, ok := get_pidinfo(pid, selection) + if !ok { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + if .PPid in selection { + info.ppid = int(ppid) + info.fields += {.PPid} + } + + if prio, has_prio := mprio.?; has_prio && .Priority in selection { + info.priority = int(prio) + info.fields += {.Priority} + } + + if .Username in selection { + pw := posix.getpwuid(uid) + if pw == nil { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + info.username = clone_string(string(pw.pw_name), allocator) or_return + info.fields += {.Username} + } + } + } + + if .Working_Dir in selection { + pinfo: darwin.proc_vnodepathinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .VNODEPATHINFO, 0, &pinfo, size_of(pinfo)) + if ret > 0 { + assert(ret == size_of(pinfo)) + info.working_dir = clone_string(string(cstring(raw_data(pinfo.pvi_cdir.vip_path[:]))), allocator) or_return + info.fields += {.Working_Dir} + } else if err == nil { + err = _get_platform_error() + } + } + + if .Executable_Path in selection { + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.pid_t(pid), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + info.executable_path = clone_string(string(buffer[:ret]), allocator) or_return + info.fields += {.Executable_Path} + } else if err == nil { + err = _get_platform_error() + } + } + + args: if selection & { .Command_Line, .Command_Args, .Environment } != {} { + mib := []i32{ + unix.CTL_KERN, + unix.KERN_PROCARGS2, + i32(pid), + } + length: uint + if sysctl(raw_data(mib), 3, nil, &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + } + break args + } + + buf := runtime.make_aligned([]byte, length, 4, temp_allocator()) + if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + + // Looks like EINVAL is returned here if you don't have permission. + if err == Platform_Error(posix.Errno.EINVAL) { + err = .Permission_Denied + } + } + break args + } + + buf = buf[:length] + + if len(buf) < 4 { + break args + } + + // Layout isn't really documented anywhere, I deduced it to be: + // i32 - argc + // cstring - command name (skipped) + // [^]byte - couple of 0 bytes (skipped) + // [^]cstring - argv (up to argc entries) + // [^]cstring - key=value env entries until the end (many intermittent 0 bytes and entries without `=` we skip here too) + + argc := (^i32)(raw_data(buf))^ + buf = buf[size_of(i32):] + + { + command_line: [dynamic]byte + command_line.allocator = allocator + + argv: [dynamic]string + argv.allocator = allocator + + defer if err != nil { + for arg in argv { delete(arg, allocator) } + delete(argv) + delete(command_line) + } + + _, _ = bytes.split_iterator(&buf, {0}) + buf = bytes.trim_left(buf, {0}) + + first_arg := true + for arg in bytes.split_iterator(&buf, {0}) { + if .Command_Line in selection { + if !first_arg { + append(&command_line, ' ') or_return + } + append(&command_line, ..arg) or_return + } + + if .Command_Args in selection { + sarg := clone_string(string(arg), allocator) or_return + append(&argv, sarg) or_return + } + + first_arg = false + argc -= 1 + if argc == 0 { + break + } + } + + if .Command_Line in selection { + info.command_line = string(command_line[:]) + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = argv[:] + info.fields += {.Command_Args} + } + } + + if .Environment in selection { + environment: [dynamic]string + environment.allocator = allocator + + defer if err != nil { + for entry in environment { delete(entry, allocator) } + delete(environment) + } + + for entry in bytes.split_iterator(&buf, {0}) { + if bytes.index_byte(entry, '=') > -1 { + sentry := clone_string(string(entry), allocator) or_return + append(&environment, sentry) or_return + } + } + + info.environment = environment[:] + info.fields += {.Environment} + } + } + + // Fields were requested that we didn't add. + if err == nil && selection - info.fields != {} { + err = .Unsupported + } + + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + ret := darwin.proc_listallpids(nil, 0) + if ret < 0 { + err = _get_platform_error() + return + } + + TEMP_ALLOCATOR_GUARD() + + buffer := make([]i32, ret, temp_allocator()) + ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) + if ret < 0 { + err = _get_platform_error() + return + } + + list = make([]int, ret, allocator) or_return + #no_bounds_check for &entry, i in list { + entry = int(buffer[i]) + } + + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(pid), .V0, &rusage); ret != 0 { + err = _get_platform_error() + return + } + + // Using the start time as the handle, there is no pidfd or anything on Darwin. + // There is a uuid, but once a process becomes a zombie it changes... + process.handle = uintptr(rusage.ri_proc_start_abstime) + process.pid = int(pid) + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return _get_platform_error() + } + + handle := uintptr(rusage.ri_proc_start_abstime) + if p.handle != handle { + return posix.Errno.ESRCH + } + + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return + } + + // NOTE(laytan): I have no clue if this is correct, the output seems correct comparing it with `time`'s output. + HZ :: 20000000 + + state.user_time = ( + (time.Duration(rusage.ri_user_time) / HZ * time.Second) + + time.Duration(rusage.ri_user_time % HZ)) + state.system_time = ( + (time.Duration(rusage.ri_system_time) / HZ * time.Second) + + time.Duration(rusage.ri_system_time % HZ)) + + return +} diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin new file mode 100644 index 000000000..02f78b9ac --- /dev/null +++ b/core/os/os2/process_posix_other.odin @@ -0,0 +1,28 @@ +//+private +//+build netbsd, openbsd, freebsd +package os2 + +import "base:runtime" + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + err = .Unsupported + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + return +} diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 47fd62401..edb321509 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -93,34 +93,11 @@ read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (byte @(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} - } - + // Note(flysand): Open the process handle right away to prevent some race + // conditions. Once the handle is open, the process will be kept alive by + // the OS. ph := win32.INVALID_HANDLE_VALUE - - if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle + if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { ph = win32.OpenProcess( win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, false, @@ -134,84 +111,15 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator 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 + snapshot_process: if selection >= {.PPid, .Priority} { entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { - err = General_Error.Not_Exist - return + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } } if .PPid in selection { info.fields += {.PPid} @@ -222,12 +130,18 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.priority = int(entry.pcPriClassBase) } } - if .Executable_Path in selection { // snap module - info.executable_path = _process_exe_by_pid(pid, allocator) or_return + snapshot_modules: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break snapshot_modules + } + info.executable_path = exe_path info.fields += {.Executable_Path} } - ph := win32.HANDLE(process.handle) - if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { process_info_size: u32 process_info: win32.PROCESS_BASIC_INFORMATION status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) @@ -235,25 +149,26 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields // TODO(flysand): There's probably a mismatch between NTSTATUS and // windows userland error codes, I haven't checked. err = Platform_Error(status) - return + break read_peb } - if process_info.PebBaseAddress == nil { - // Not sure what the error is - err = General_Error.Unsupported - return - } - + assert(process_info.PebBaseAddress != nil) process_peb: win32.PEB - _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return - + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } process_params: win32.RTL_USER_PROCESS_PARAMETERS - _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return - + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } 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 - + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } if .Command_Line in selection { info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return info.fields += {.Command_Line} @@ -263,28 +178,147 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields 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 + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } + 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 - + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } 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 + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username + 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 + // Data obtained from process snapshots + snapshot_process: if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + snapshot_module: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break snapshot_module + } + info.executable_path = exe_path + info.fields += {.Executable_Path} + } + ph := win32.HANDLE(process.handle) + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { + 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 + } + assert(process_info.PebBaseAddress != nil) + process_peb: win32.PEB + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } + if selection >= {.Command_Line, .Command_Args} { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } + 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 + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } + 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 + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username info.fields += {.Username} } err = nil @@ -294,15 +328,15 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields @(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 + snapshot_process: if selection >= {.PPid, .Priority} { entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { - err = General_Error.Not_Exist - return + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } } if .PPid in selection { info.fields += {.PPid} @@ -313,14 +347,16 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.priority = int(entry.pcPriClassBase) } } - if .Executable_Path in selection { + module_filename: if .Executable_Path in selection { exe_filename_w: [256]u16 path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + assert(path_len > 0) 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: if selection >= {.Command_Line, .Command_Args} { command_line_w := win32.GetCommandLineW() + assert(command_line_w != nil) if .Command_Line in selection { info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return info.fields += {.Command_Line} @@ -330,14 +366,22 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.fields += {.Command_Args} } } - if .Environment in selection { + read_environment: if .Environment in selection { env_block := win32.GetEnvironmentStringsW() + assert(env_block != nil) info.environment = _parse_environment_block(env_block, allocator) or_return info.fields += {.Environment} } - if .Username in selection { + read_username: if .Username in selection { process_handle := win32.GetCurrentProcess() - info.username = _get_process_user(process_handle, allocator) or_return + username: string + username, err = _get_process_user(process_handle, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username info.fields += {.Username} } if .Working_Dir in selection { diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index b3ca47be3..b53ebb3ab 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -30,8 +30,8 @@ file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: } 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) + #reverse for info in infos { + file_info_delete(info, allocator) } delete(infos, allocator) } diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin new file mode 100644 index 000000000..a817a862b --- /dev/null +++ b/core/os/os2/stat_posix.odin @@ -0,0 +1,137 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sys/posix" +import "core:time" + +internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + fi.name = filepath.base(fi.fullpath) + + fi.inode = u128(stat.st_ino) + fi.size = i64(stat.st_size) + + fi.mode = int(transmute(posix._mode_t)(stat.st_mode - posix.S_IFMT)) + + fi.type = .Undetermined + switch { + case posix.S_ISBLK(stat.st_mode): + fi.type = .Block_Device + case posix.S_ISCHR(stat.st_mode): + fi.type = .Character_Device + case posix.S_ISDIR(stat.st_mode): + fi.type = .Directory + case posix.S_ISFIFO(stat.st_mode): + fi.type = .Named_Pipe + case posix.S_ISLNK(stat.st_mode): + fi.type = .Symlink + case posix.S_ISREG(stat.st_mode): + fi.type = .Regular + case posix.S_ISSOCK(stat.st_mode): + fi.type = .Socket + } + + fi.creation_time = timespec_time(stat.st_birthtimespec) + fi.modification_time = timespec_time(stat.st_mtim) + fi.access_time = timespec_time(stat.st_atim) + + timespec_time :: proc(t: posix.timespec) -> time.Time { + return time.Time{_nsec = i64(t.tv_sec) * 1e9 + i64(t.tv_nsec)} + } + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat: posix.stat_t + if posix.fstat(impl.fd, &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) or_return + + fd := posix.open(cname, {}) + if fd == -1 { + err = _get_platform_error() + return + } + defer posix.close(fd) + + fullpath := _posix_absolute_path(fd, name, allocator) or_return + + stat: posix.stat_t + if posix.stat(fullpath, &stat) != .OK { + err = _get_platform_error() + return + } + + return internal_stat(stat, string(fullpath)), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + TEMP_ALLOCATOR_GUARD() + + // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. + + // NOTE: This might not be correct when given "/symlink/foo.txt", + // you would want that to resolve "/symlink", but not resolve "foo.txt". + + fullpath := filepath.clean(name, temp_allocator()) + assert(len(fullpath) > 0) + switch { + case fullpath[0] == '/': + // nothing. + case fullpath == ".": + fullpath = getwd(temp_allocator()) or_return + case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': + fullpath = fullpath[2:] + fallthrough + case: + fullpath = concatenate({ + getwd(temp_allocator()) or_return, + "/", + fullpath, + }, temp_allocator()) or_return + } + + stat: posix.stat_t + if posix.lstat(temp_cstring(fullpath), &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath = clone_string(fullpath, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 5e66507be..566417c84 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -44,6 +44,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path if name == "" { name = "." } + TEMP_ALLOCATOR_GUARD() p := win32_utf8_to_utf16(name, temp_allocator()) or_return @@ -127,7 +128,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin if n == 0 { return "", _get_platform_error() } + TEMP_ALLOCATOR_GUARD() + buf := make([]u16, max(n, 260)+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_from_buf(buf[:n], allocator) @@ -143,7 +146,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { if n == 0 { return nil, _get_platform_error() } + TEMP_ALLOCATOR_GUARD() + buf := make([]u16, max(n, 260)+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_strip_prefix(buf[:n]), nil diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 467775e89..5ca4e1453 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -10,7 +10,7 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? // Opens the file for reading and writing, with 0o666 permissions, and returns the new `^File`. // The filename is generated by taking a pattern, and adding a randomized string to the end. // If the pattern includes an "*", the random string replaces the last "*". -// If `dir` is an empty tring, `temp_directory()` will be used. +// If `dir` is an empty string, `temp_directory()` will be used. // // The caller must `close` the file once finished with. @(require_results) diff --git a/core/os/os2/temp_file_posix.odin b/core/os/os2/temp_file_posix.odin new file mode 100644 index 000000000..67ec4d3e8 --- /dev/null +++ b/core/os/os2/temp_file_posix.odin @@ -0,0 +1,20 @@ +//+private +//+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +@(require) +import "core:sys/posix" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + if tmp, ok := _lookup_env("TMPDIR", allocator); ok { + return tmp, nil + } + + when #defined(posix.P_tmpdir) { + return clone_string(posix.P_tmpdir, allocator) + } + + return clone_string("/tmp/", allocator) +} diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index ca099f7ae..a0a7a839d 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -4,21 +4,23 @@ import "base:runtime" @(require_results) user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + #partial switch ODIN_OS { case .Windows: - dir = get_env("LocalAppData", allocator) + dir = get_env("LocalAppData", temp_allocator()) if dir != "" { dir = clone_string(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir != "" { dir = concatenate({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) if dir == "" { - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir == "" { return } @@ -33,21 +35,23 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error @(require_results) user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + #partial switch ODIN_OS { case .Windows: - dir = get_env("AppData", allocator) + dir = get_env("AppData", temp_allocator()) if dir != "" { dir = clone_string(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir != "" { - dir = concatenate({dir, "/Library/Application Support"}, allocator) or_return + dir = concatenate({dir, "/.config"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) if dir == "" { - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir == "" { return } diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 09cdd84d0..2bb6c0c59 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -492,6 +492,10 @@ in6_addr :: struct #packed { s6_addr: [16]u8, } +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/socket.h#L1025-L1027 +// Prevent the raising of SIGPIPE on writing to a closed network socket. +MSG_NOSIGNAL :: 0x80000 + SIOCGIFFLAG :: enum c.int { UP = 0, /* Interface is up. */ BROADCAST = 1, /* Broadcast address valid. */ @@ -580,7 +584,7 @@ F_GETPATH :: 50 // return the full path of the fd foreign libc { @(link_name="__error") __error :: proc() -> ^c.int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg args: ..any) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg mode: ..u16) -> Handle --- @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @@ -594,7 +598,8 @@ foreign libc { @(link_name="fstat64") _unix_fstat :: proc(fd: Handle, stat: ^OS_Stat) -> c.int --- @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="fsync") _unix_fsync :: proc(handle: Handle) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(handle: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(handle: Handle) -> Handle --- @(link_name="fdopendir$INODE64") _unix_fdopendir_amd64 :: proc(fd: Handle) -> Dir --- @(link_name="readdir_r$INODE64") _unix_readdir_r_amd64 :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- @@ -623,23 +628,23 @@ foreign libc { @(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 --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- - @(link_name="socket") _unix_socket :: proc(domain: int, type: int, protocol: int) -> int --- - @(link_name="listen") _unix_listen :: proc(socket: int, backlog: int) -> int --- - @(link_name="accept") _unix_accept :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int --- - @(link_name="connect") _unix_connect :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- - @(link_name="bind") _unix_bind :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- - @(link_name="setsockopt") _unix_setsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int --- - @(link_name="getsockopt") _unix_getsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int --- - @(link_name="recvfrom") _unix_recvfrom :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- - @(link_name="recv") _unix_recv :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- - @(link_name="sendto") _unix_sendto :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- - @(link_name="send") _unix_send :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- - @(link_name="shutdown") _unix_shutdown :: proc(socket: int, how: int) -> int --- + @(link_name="socket") _unix_socket :: proc(domain: c.int, type: c.int, protocol: c.int) -> c.int --- + @(link_name="listen") _unix_listen :: proc(socket: c.int, backlog: c.int) -> c.int --- + @(link_name="accept") _unix_accept :: proc(socket: c.int, addr: rawptr, addr_len: rawptr) -> c.int --- + @(link_name="connect") _unix_connect :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="bind") _unix_bind :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="setsockopt") _unix_setsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: socklen_t) -> c.int --- + @(link_name="getsockopt") _unix_getsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: ^socklen_t) -> c.int --- + @(link_name="recvfrom") _unix_recvfrom :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- + @(link_name="recv") _unix_recv :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="sendto") _unix_sendto :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- + @(link_name="send") _unix_send :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="shutdown") _unix_shutdown :: proc(socket: c.int, how: c.int) -> c.int --- @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- @@ -656,9 +661,9 @@ when ODIN_ARCH != .arm64 { } foreign dl { - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: int) -> rawptr --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> int --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } @@ -693,18 +698,6 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (handle: Handl return } - /* - @INFO(Platin): this is only done because O_CREATE for some reason fails to apply mode - should not happen if the handle is a directory - */ - if mode != 0 && !isDir { - err = fchmod(handle, cast(u16)mode) - if err != nil { - _unix_close(handle) - handle = INVALID_HANDLE - } - } - return } @@ -784,10 +777,21 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { assert(fd != -1) + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) if final_offset == -1 { - return 0, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return final_offset, nil } @@ -1005,6 +1009,15 @@ _readlink :: proc(path: string) -> (string, Error) { } } +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + @(require_results) absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { buf: [DARWIN_MAXPATHLEN]byte @@ -1026,10 +1039,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := cast(cstring)path_ptr - path = strings.clone(string(path_cstr)) + path = strings.clone(string(path_ptr)) return path, nil } @@ -1140,7 +1152,7 @@ current_thread_id :: proc "contextless" () -> int { 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) + handle := _unix_dlopen(cstr, c.int(flags)) return handle } @(require_results) @@ -1194,26 +1206,24 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -@(require_results) socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { - result := _unix_socket(domain, type, protocol) + result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) if result < 0 { return 0, get_last_error() } return Socket(result), nil } -@(require_results) connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { - result := _unix_connect(int(sd), addr, len) + result := _unix_connect(c.int(sd), addr, len) if result < 0 { return get_last_error() } return nil } -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { - result := _unix_bind(int(sd), addr, len) +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Error) { + result := _unix_bind(c.int(sd), addr, len) if result < 0 { return get_last_error() } @@ -1221,15 +1231,15 @@ bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { } accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { - result := _unix_accept(int(sd), rawptr(addr), len) + result := _unix_accept(c.int(sd), rawptr(addr), len) if result < 0 { return 0, get_last_error() } return Socket(result), nil } -listen :: proc(sd: Socket, backlog: int) -> Error { - result := _unix_listen(int(sd), backlog) +listen :: proc(sd: Socket, backlog: int) -> (Error) { + result := _unix_listen(c.int(sd), c.int(backlog)) if result < 0 { return get_last_error() } @@ -1237,7 +1247,7 @@ listen :: proc(sd: Socket, backlog: int) -> Error { } setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { - result := _unix_setsockopt(int(sd), level, optname, optval, optlen) + result := _unix_setsockopt(c.int(sd), c.int(level), c.int(optname), optval, optlen) if result < 0 { return get_last_error() } @@ -1245,7 +1255,8 @@ setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: } getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { - result := _unix_getsockopt(int(sd), level, optname, optval, optlen) + optlen := optlen + result := _unix_getsockopt(c.int(sd), c.int(level), c.int(optname), optval, &optlen) if result < 0 { return get_last_error() } @@ -1253,7 +1264,7 @@ getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: } 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) + result := _unix_recvfrom(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addr_size) if result < 0 { return 0, get_last_error() } @@ -1261,7 +1272,7 @@ recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_siz } recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := _unix_recv(int(sd), raw_data(data), len(data), flags) + result := _unix_recv(c.int(sd), raw_data(data), len(data), c.int(flags)) if result < 0 { return 0, get_last_error() } @@ -1269,7 +1280,7 @@ recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { } 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) + result := _unix_sendto(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addrlen) if result < 0 { return 0, get_last_error() } @@ -1277,15 +1288,15 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc } send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := _unix_send(int(sd), raw_data(data), len(data), 0) + result := _unix_send(c.int(sd), raw_data(data), len(data), 0) if result < 0 { return 0, get_last_error() } return u32(result), nil } -shutdown :: proc(sd: Socket, how: int) -> Error { - result := _unix_shutdown(int(sd), how) +shutdown :: proc(sd: Socket, how: int) -> (Error) { + result := _unix_shutdown(c.int(sd), c.int(how)) if result < 0 { return get_last_error() } diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index c7955368e..c05a06129 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -6,6 +6,7 @@ foreign import libc "system:c" import "base:runtime" import "core:strings" import "core:c" +import "core:sys/freebsd" Handle :: distinct i32 File_Time :: distinct u64 @@ -371,7 +372,7 @@ F_KINFO :: 22 foreign libc { @(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="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- @@ -388,7 +389,8 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @@ -401,7 +403,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -429,7 +431,7 @@ get_last_error :: proc "contextless" () -> Error { 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)) + handle := _unix_open(cstr, c.int(flags), u16(mode)) if handle == -1 { return INVALID_HANDLE, get_last_error() } @@ -445,8 +447,7 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing - return nil + return cast(_Platform_Error)freebsd.fsync(cast(freebsd.Fd)fd) } // If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`. @@ -480,29 +481,45 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } 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 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read, errno := freebsd.pread(cast(freebsd.Fd)fd, data[:to_read], cast(freebsd.off_t)offset) + + return bytes_read, cast(_Platform_Error)errno } 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 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written, errno := freebsd.pwrite(cast(freebsd.Fd)fd, data[:to_write], cast(freebsd.off_t)offset) + + return bytes_written, cast(_Platform_Error)errno } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case: + return 0, errno + } } return res, nil } @@ -739,6 +756,15 @@ _readlink :: proc(path: string) -> (string, Error) { return "", Error{} } +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + @(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 @@ -776,10 +802,10 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 7f1ec7089..0d2c334be 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -119,49 +119,52 @@ S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK foreign libc { - @(link_name="_errorp") __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 --- + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> 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 --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(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="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(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="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } MAXNAMLEN :: haiku.NAME_MAX @@ -200,7 +203,7 @@ fork :: proc() -> (Pid, Error) { 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)) + handle := _unix_open(cstr, c.int(flags), u16(mode)) if handle == -1 { return INVALID_HANDLE, get_last_error() } @@ -216,7 +219,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } @@ -250,29 +256,48 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } 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 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } 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 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .BAD_VALUE: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 1e110d18f..9132edbfe 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -7,7 +7,6 @@ import "base:runtime" import "core:strings" import "core:c" import "core:strconv" -import "base:intrinsics" // NOTE(flysand): For compatibility we'll make core:os package // depend on the old (scheduled for removal) linux package. @@ -263,7 +262,7 @@ Unix_File_Time :: struct { nanoseconds: i64, } -when ODIN_ARCH == .arm64 { +when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { OS_Stat :: struct { device_id: u64, // ID of device containing file serial: u64, // File serial number @@ -396,9 +395,9 @@ SIOCGIFFLAG :: enum c.int { PORTSEL = 13, /* Can set media type. */ AUTOMEDIA = 14, /* Auto media select active. */ DYNAMIC = 15, /* Dialup device with changing addresses. */ - LOWER_UP = 16, - DORMANT = 17, - ECHO = 18, + LOWER_UP = 16, + DORMANT = 17, + ECHO = 18, } SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] @@ -492,7 +491,7 @@ foreign libc { @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- } @@ -585,8 +584,7 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing - return nil + return _get_errno(unix.sys_fsync(int(fd))) } // If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error). @@ -655,9 +653,20 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := unix.sys_lseek(int(fd), offset, whence) if res < 0 { - return -1, _get_errno(int(res)) + errno := _get_errno(int(res)) + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return i64(res), nil } @@ -887,6 +896,12 @@ _readlink :: proc(path: string) -> (string, Error) { } } +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup, err := linux.dup(linux.Fd(fd)) + return Handle(dup), err +} + @(require_results) absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { buf : [256]byte @@ -912,9 +927,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index c41dc6aa6..a56c0b784 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -330,7 +330,7 @@ dev_t :: u64 ino_t :: u64 nlink_t :: u32 off_t :: i64 -mode_t :: u16 +mode_t :: u32 pid_t :: u32 uid_t :: u32 gid_t :: u32 @@ -423,10 +423,12 @@ R_OK :: 4 // Test for read permission foreign libc { @(link_name="__errno") __errno_location :: proc() -> ^c.int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- @@ -440,7 +442,9 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @@ -453,7 +457,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -487,7 +491,7 @@ get_last_error :: proc "contextless" () -> Error { 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)) + handle := _unix_open(cstr, c.int(flags), c.uint(mode)) if handle == -1 { return INVALID_HANDLE, get_last_error() } @@ -503,7 +507,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } @@ -534,29 +541,48 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } 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 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } 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 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } @@ -801,6 +827,15 @@ _readlink :: proc(path: string) -> (string, Error) { return "", Error{} } +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + @(require_results) absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { buf: [MAX_PATH]byte @@ -822,9 +857,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index 1cd26211e..aff78dc60 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -343,49 +343,53 @@ AT_REMOVEDIR :: 0x08 @(default_calling_convention="c") foreign libc { - @(link_name="__error") __error :: proc() -> ^c.int --- + @(link_name="__error") __error :: proc() -> ^c.int --- - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> 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 --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(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="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(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="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } @(require_results) @@ -411,7 +415,7 @@ fork :: proc() -> (Pid, Error) { 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)) + handle := _unix_open(cstr, c.int(flags), c.uint(mode)) if handle == -1 { return INVALID_HANDLE, get_last_error() } @@ -427,7 +431,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } @@ -462,29 +469,48 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } 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 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } 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 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } @@ -716,6 +742,15 @@ _readlink :: proc(path: string) -> (string, Error) { } } +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + // XXX OpenBSD @(require_results) absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { @@ -736,9 +771,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 273fe5af0..6fb0631cd 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -43,6 +43,7 @@ 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_NEGATIVE_SEEK :: _Platform_Error(131) ERROR_DIR_NOT_EMPTY :: _Platform_Error(145) ERROR_ALREADY_EXISTS :: _Platform_Error(183) ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203) @@ -91,6 +92,9 @@ get_last_error :: proc "contextless" () -> Error { case win32.ERROR_INVALID_HANDLE: return .Invalid_File + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, diff --git a/core/os/stream.odin b/core/os/stream.odin index 8acbee489..39edc9cd5 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -21,6 +21,9 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Flush: os_err = flush(fd) case .Read: + if len(p) == 0 { + return 0, nil + } n_int, os_err = read(fd, p) n = i64(n_int) if n == 0 && os_err == nil { @@ -28,18 +31,27 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Read_At: + if len(p) == 0 { + return 0, nil + } n_int, os_err = read_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == nil { err = .EOF } case .Write: + if len(p) == 0 { + return 0, nil + } n_int, os_err = write(fd, p) n = i64(n_int) if n == 0 && os_err == nil { err = .EOF } case .Write_At: + if len(p) == 0 { + return 0, nil + } n_int, os_err = write_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == nil { @@ -58,5 +70,8 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, if err == nil && os_err != nil { err = error_to_io_error(os_err) } + if err != nil { + n = 0 + } return } diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index c3dfa2bb1..e23183b02 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -2,6 +2,7 @@ // To process paths such as URLs that depend on forward slashes regardless of the OS, use the path package package filepath +import "base:runtime" import "core:strings" SEPARATOR_CHARS :: `/\` @@ -244,7 +245,7 @@ long_ext :: proc(path: string) -> string { If the result of the path is an empty string, the returned path with be `"."`. */ -clean :: proc(path: string, allocator := context.allocator) -> string { +clean :: proc(path: string, allocator := context.allocator) -> (cleaned: string, err: runtime.Allocator_Error) #optional_allocator_error { context.allocator = allocator path := path @@ -256,9 +257,9 @@ clean :: proc(path: string, allocator := context.allocator) -> string { if vol_len > 1 && original_path[1] != ':' { s, ok := from_slash(original_path) if !ok { - s = strings.clone(s) + s = strings.clone(s) or_return } - return s + return s, nil } return strings.concatenate({original_path, "."}) } @@ -275,7 +276,7 @@ clean :: proc(path: string, allocator := context.allocator) -> string { r, dot_dot := 0, 0 if rooted { - lazy_buffer_append(out, SEPARATOR) + lazy_buffer_append(out, SEPARATOR) or_return r, dot_dot = 1, 1 } @@ -295,33 +296,35 @@ clean :: proc(path: string, allocator := context.allocator) -> string { } case !rooted: if out.w > 0 { - lazy_buffer_append(out, SEPARATOR) + lazy_buffer_append(out, SEPARATOR) or_return } - lazy_buffer_append(out, '.') - lazy_buffer_append(out, '.') + lazy_buffer_append(out, '.') or_return + lazy_buffer_append(out, '.') or_return dot_dot = out.w } case: if rooted && out.w != 1 || !rooted && out.w != 0 { - lazy_buffer_append(out, SEPARATOR) + lazy_buffer_append(out, SEPARATOR) or_return } for ; r < n && !is_separator(path[r]); r += 1 { - lazy_buffer_append(out, path[r]) + lazy_buffer_append(out, path[r]) or_return } } } if out.w == 0 { - lazy_buffer_append(out, '.') + lazy_buffer_append(out, '.') or_return } - s := lazy_buffer_string(out) - cleaned, new_allocation := from_slash(s) + s := lazy_buffer_string(out) or_return + + new_allocation: bool + cleaned, new_allocation = from_slash(s) if new_allocation { delete(s) } - return cleaned + return } // Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character. @@ -453,9 +456,9 @@ dir :: proc(path: string, allocator := context.allocator) -> string { // An empty string returns nil. A non-empty string with no separators returns a 1-element array. // Any empty components will be included, e.g. `a::b` will return a 3-element array, as will `::`. // Separators within pairs of double-quotes will be ignored and stripped, e.g. `"a:b"c:d` will return []{`a:bc`, `d`}. -split_list :: proc(path: string, allocator := context.allocator) -> []string { +split_list :: proc(path: string, allocator := context.allocator) -> (list: []string, err: runtime.Allocator_Error) #optional_allocator_error { if path == "" { - return nil + return nil, nil } start: int @@ -475,7 +478,7 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { } start, quote = 0, false - list := make([]string, count + 1, allocator) + list = make([]string, count + 1, allocator) or_return index := 0 for i := 0; i < len(path); i += 1 { c := path[i] @@ -494,12 +497,12 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { for s0, i in list { s, new := strings.replace_all(s0, `"`, ``, allocator) if !new { - s = strings.clone(s, allocator) + s = strings.clone(s, allocator) or_return } list[i] = s } - return list + return list, nil } @@ -526,33 +529,35 @@ lazy_buffer_index :: proc(lb: ^Lazy_Buffer, i: int) -> byte { return lb.s[i] } @(private) -lazy_buffer_append :: proc(lb: ^Lazy_Buffer, c: byte) { +lazy_buffer_append :: proc(lb: ^Lazy_Buffer, c: byte) -> (err: runtime.Allocator_Error) { if lb.b == nil { if lb.w < len(lb.s) && lb.s[lb.w] == c { lb.w += 1 return } - lb.b = make([]byte, len(lb.s)) + lb.b = make([]byte, len(lb.s)) or_return copy(lb.b, lb.s[:lb.w]) } lb.b[lb.w] = c lb.w += 1 + return } @(private) -lazy_buffer_string :: proc(lb: ^Lazy_Buffer) -> string { +lazy_buffer_string :: proc(lb: ^Lazy_Buffer) -> (s: string, err: runtime.Allocator_Error) { if lb.b == nil { return strings.clone(lb.vol_and_path[:lb.vol_len+lb.w]) } x := lb.vol_and_path[:lb.vol_len] y := string(lb.b[:lb.w]) - z := make([]byte, len(x)+len(y)) + z := make([]byte, len(x)+len(y)) or_return copy(z, x) copy(z[len(x):], y) - return string(z) + return string(z), nil } @(private) -lazy_buffer_destroy :: proc(lb: ^Lazy_Buffer) { - delete(lb.b) +lazy_buffer_destroy :: proc(lb: ^Lazy_Buffer) -> runtime.Allocator_Error { + err := delete(lb.b) lb^ = {} + return err } diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index b44a6a344..7137ad844 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -32,27 +32,26 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { if path_ptr == nil { return "", __error()^ == 0 } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := cstring(path_ptr) - path_str := strings.clone(string(path_cstr), allocator) + path_str := strings.clone(string(path_ptr), allocator) return path_str, true } -join :: proc(elems: []string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { for e, i in elems { if e != "" { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) + p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return return clean(p, allocator) } } - return "" + return "", nil } @(private) foreign libc { - realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="free") _unix_free :: proc(ptr: rawptr) --- } diff --git a/core/prof/spall/doc.odin b/core/prof/spall/doc.odin index d9259465b..c81bad05f 100644 --- a/core/prof/spall/doc.odin +++ b/core/prof/spall/doc.odin @@ -1,4 +1,7 @@ /* +Example: + package main + import "base:runtime" import "core:prof/spall" import "core:sync" diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index 174b3a11b..fc05b8525 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -5,22 +5,7 @@ package spall // Only for types. import "core:os" -when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" -} else { - foreign import libc "system:c" -} - -timespec :: struct { - tv_sec: i64, // seconds - tv_nsec: i64, // nanoseconds -} - -foreign libc { - __error :: proc() -> ^i32 --- - @(link_name="write") _unix_write :: proc(handle: os.Handle, buffer: rawptr, count: uint) -> int --- - @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 --- -} +import "core:sys/posix" MAX_RW :: 0x7fffffff @@ -32,7 +17,7 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.E for n < len(data) { chunk := data[:min(len(data), MAX_RW)] - written := _unix_write(fd, raw_data(chunk), len(chunk)) + written := posix.write(posix.FD(fd), raw_data(chunk), len(chunk)) if written < 0 { return n, os.get_last_error() } @@ -42,11 +27,17 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.E return n, nil } -CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. +// NOTE(tetra): "RAW" means: Not adjusted by NTP. +when ODIN_OS == .Darwin { + CLOCK :: posix.Clock(4) // CLOCK_MONOTONIC_RAW +} else { + // It looks like the BSDs don't have a CLOCK_MONOTONIC_RAW equivalent. + CLOCK :: posix.Clock.MONOTONIC +} @(no_instrumentation) _tick_now :: proc "contextless" () -> (ns: i64) { - t: timespec - _unix_clock_gettime(CLOCK_MONOTONIC_RAW, &t) - return t.tv_sec*1e9 + t.tv_nsec + t: posix.timespec + posix.clock_gettime(CLOCK, &t) + return i64(t.tv_sec)*1e9 + i64(t.tv_nsec) } diff --git a/core/reflect/iterator.odin b/core/reflect/iterator.odin index 5b84f0133..090fe04cc 100644 --- a/core/reflect/iterator.odin +++ b/core/reflect/iterator.odin @@ -19,6 +19,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(val.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } case Type_Info_Slice: @@ -27,6 +28,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } case Type_Info_Dynamic_Array: @@ -35,6 +37,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } } @@ -69,10 +72,12 @@ iterate_map :: proc(val: any, it: ^int) -> (key, value: any, ok: bool) { key.id = info.key.id value.id = info.value.id ok = true + it^ += 1 break } } } return -} \ No newline at end of file +} + diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 23c0f803e..c04afb380 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -496,6 +496,64 @@ struct_field_offsets :: proc(T: typeid) -> []uintptr { return nil } +Struct_Field_Count_Method :: enum { + Top_Level, + Using, + Recursive, +} + +/* +Counts the number of fields in a struct + +This procedure returns the number of fields in a struct, counting in one of three ways: +- .Top_Level: Only counts the top-level fields +- .Using: Same count as .Top_Level, and adds the field count of any `using s: Struct` it encounters (in addition to itself) +- .Recursive: The count of all top-level fields, plus the count of any child struct's fields, recursively + +Inputs: +- T: The struct type +- method: The counting method + +Returns: +- The `count`, enumerated using the `method`, which will be `0` if the type is not a struct + +Example: + symbols_loaded, ok := dynlib.initialize_symbols(&game_api, "game.dll") + symbols_expected := reflect.struct_field_count(Game_Api) - API_PRIVATE_COUNT + + if symbols_loaded != symbols_expected { + fmt.eprintf("Expected %v symbols, got %v", symbols_expected, symbols_loaded) + return + } +*/ +@(require_results) +struct_field_count :: proc(T: typeid, method := Struct_Field_Count_Method.Top_Level) -> (count: int) { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + switch method { + case .Top_Level: + return int(s.field_count) + + case .Using: + count = int(s.field_count) + for type, i in s.types[:s.field_count] { + if s.usings[i] { + count += struct_field_count(type.id) + } + } + + case .Recursive: + count = int(s.field_count) + for type in s.types[:s.field_count] { + count += struct_field_count(type.id) + } + + case: return 0 + } + } + return +} + @(require_results) struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) @@ -709,7 +767,7 @@ union_variant_type_info :: proc(a: any) -> ^Type_Info { @(require_results) type_info_union_is_pure_maybe :: proc(info: runtime.Type_Info_Union) -> bool { - return len(info.variants) == 1 && is_pointer(info.variants[0]) + return len(info.variants) == 1 && is_pointer_internally(info.variants[0]) } @(require_results) @@ -957,6 +1015,74 @@ bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool { } +Bit_Field :: struct { + name: string, + type: ^Type_Info, + size: uintptr, // Size in bits + offset: uintptr, // Offset in bits + tag: Struct_Tag, +} + +@(require_results) +bit_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Bit_Field) { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return soa_zip( + name = s.names[:s.field_count], + type = s.types[:s.field_count], + size = s.bit_sizes[:s.field_count], + offset = s.bit_offsets[:s.field_count], + tag = ([^]Struct_Tag)(s.tags)[:s.field_count], + ) + } + return nil +} + +@(require_results) +bit_field_names :: proc(T: typeid) -> []string { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.names[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_types :: proc(T: typeid) -> []^Type_Info { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.types[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_sizes :: proc(T: typeid) -> []uintptr { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.bit_sizes[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_offsets :: proc(T: typeid) -> []uintptr { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.bit_offsets[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_tags :: proc(T: typeid) -> []Struct_Tag { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return transmute([]Struct_Tag)s.tags[:s.field_count] + } + return nil +} + @(require_results) as_bool :: proc(a: any) -> (value: bool, valid: bool) { if a == nil { return } @@ -1640,4 +1766,4 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ runtime.print_typeid(a.id) runtime.print_string("\n") return true -} +} \ No newline at end of file diff --git a/core/simd/simd.odin b/core/simd/simd.odin index c5a594df6..01d11dfbe 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -3,6 +3,14 @@ package simd import "base:builtin" import "base:intrinsics" +// IS_EMULATED is true iff the compile-time target lacks hardware support +// for at least 128-bit SIMD. +IS_EMULATED :: true when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else + true when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else + true when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else + true when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else + false + // 128-bit vector aliases u8x16 :: #simd[16]u8 i8x16 :: #simd[16]i8 @@ -74,8 +82,8 @@ shl_masked :: intrinsics.simd_shl_masked shr_masked :: intrinsics.simd_shr_masked // Saturation Arithmetic -add_sat :: intrinsics.simd_add_sat -sub_sat :: intrinsics.simd_sub_sat +saturating_add :: intrinsics.simd_saturating_add +saturating_sub :: intrinsics.simd_saturating_sub bit_and :: intrinsics.simd_bit_and bit_or :: intrinsics.simd_bit_or @@ -102,6 +110,15 @@ lanes_le :: intrinsics.simd_lanes_le lanes_gt :: intrinsics.simd_lanes_gt lanes_ge :: intrinsics.simd_lanes_ge + +// Gather and Scatter intrinsics +gather :: intrinsics.simd_gather +scatter :: intrinsics.simd_scatter +masked_load :: intrinsics.simd_masked_load +masked_store :: intrinsics.simd_masked_store +masked_expand_load :: intrinsics.simd_masked_expand_load +masked_compress_store :: intrinsics.simd_masked_compress_store + // extract :: proc(a: #simd[N]T, idx: uint) -> T extract :: intrinsics.simd_extract // replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T @@ -115,6 +132,9 @@ reduce_and :: intrinsics.simd_reduce_and reduce_or :: intrinsics.simd_reduce_or reduce_xor :: intrinsics.simd_reduce_xor +reduce_any :: intrinsics.simd_reduce_any +reduce_all :: intrinsics.simd_reduce_all + // swizzle :: proc(a: #simd[N]T, indices: ..int) -> #simd[len(indices)]T swizzle :: builtin.swizzle diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index 426359031..2e3eb8523 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -39,19 +39,19 @@ _mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add_sat(transmute(i8x16)a, transmute(i8x16)b) + return transmute(__m128i)simd.saturating_add(transmute(i8x16)a, transmute(i8x16)b) } @(require_results, enable_target_feature="sse2") _mm_adds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add_sat(transmute(i16x8)a, transmute(i16x8)b) + return transmute(__m128i)simd.saturating_add(transmute(i16x8)a, transmute(i16x8)b) } @(require_results, enable_target_feature="sse2") _mm_adds_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add_sat(transmute(u8x16)a, transmute(u8x16)b) + return transmute(__m128i)simd.saturating_add(transmute(u8x16)a, transmute(u8x16)b) } @(require_results, enable_target_feature="sse2") _mm_adds_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add_sat(transmute(u16x8)a, transmute(u16x8)b) + return transmute(__m128i)simd.saturating_add(transmute(u16x8)a, transmute(u16x8)b) } @(require_results, enable_target_feature="sse2") _mm_avg_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -122,19 +122,19 @@ _mm_sub_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_subs_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub_sat(transmute(i8x16)a, transmute(i8x16)b) + return transmute(__m128i)simd.saturating_sub(transmute(i8x16)a, transmute(i8x16)b) } @(require_results, enable_target_feature="sse2") _mm_subs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub_sat(transmute(i16x8)a, transmute(i16x8)b) + return transmute(__m128i)simd.saturating_sub(transmute(i16x8)a, transmute(i16x8)b) } @(require_results, enable_target_feature="sse2") _mm_subs_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub_sat(transmute(u8x16)a, transmute(u8x16)b) + return transmute(__m128i)simd.saturating_sub(transmute(u8x16)a, transmute(u8x16)b) } @(require_results, enable_target_feature="sse2") _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub_sat(transmute(u16x8)a, transmute(u16x8)b) + return transmute(__m128i)simd.saturating_sub(transmute(u16x8)a, transmute(u16x8)b) } diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 043e51aa5..99ad15547 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -160,8 +160,26 @@ binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) return binary_search_by(array, key, cmp_proc(T)) } +/* + Binary search searches the given slice for the given element. + If the slice is not sorted, the returned index is unspecified and meaningless. + + If the value is found then the returned int is the index of the matching element. + If there are multiple matches, then any one of the matches could be returned. + + If the value is not found then the returned int is the index where a matching + element could be inserted while maintaining sorted order. + + The array elements and key may be different types. This allows the filter procedure + to compare keys against a slice of structs, one struct value at a time. + + Returns: + index: int + found: bool + +*/ @(require_results) -binary_search_by :: proc(array: $A/[]$T, key: T, f: proc(T, T) -> Ordering) -> (index: int, found: bool) #no_bounds_check { +binary_search_by :: proc(array: $A/[]$T, key: $K, f: proc(T, K) -> Ordering) -> (index: int, found: bool) #no_bounds_check { n := len(array) left, right := 0, n for left < right { @@ -173,8 +191,6 @@ binary_search_by :: proc(array: $A/[]$T, key: T, f: proc(T, T) -> Ordering) -> ( right = mid } } - // left == right - // f(array[left-1], key) == .Less (if left > 0) return left, left < n && f(array[left], key) == .Equal } @@ -184,6 +200,17 @@ equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) #no_ return false } when intrinsics.type_is_simple_compare(E) { + if len(a) == 0 { + // Empty slices are always equivalent to each other. + // + // This check is here in the event that a slice with a `data` of + // nil is compared against a slice with a non-nil `data` but a + // length of zero. + // + // In that case, `memory_compare` would return -1 or +1 because one + // of the pointers is nil. + return true + } return runtime.memory_compare(raw_data(a), raw_data(b), len(a)*size_of(E)) == 0 } else { for i in 0.. (result: bool = false, ok: bool) /* Finds the integer value of the given rune -**Inputs** +**Inputs** - r: The input rune to find the integer value of **Returns** The integer value of the given rune @@ -47,7 +47,7 @@ _digit_value :: proc(r: rune) -> int { /* Parses an integer value from the input string in the given base, without a prefix -**Inputs** +**Inputs** - str: The input string to parse the integer value from - base: The base of the integer value to be parsed (must be between 1 and 16) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -65,7 +65,7 @@ Output: -1234 false -**Returns** +**Returns** - value: Parses an integer value from a string, in the given base, without a prefix. - ok: ok=false if no numeric value of the appropriate base could be found, or if the input string contained more than just the number. */ @@ -117,12 +117,12 @@ parse_i64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i64, /* Parses an integer value from the input string in base 10, unless there's a prefix -**Inputs** +**Inputs** - str: The input string to parse the integer value from - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_i64_maybe_prefixed_example :: proc() { @@ -132,13 +132,13 @@ Example: n, ok = strconv.parse_i64_maybe_prefixed("0xeeee") fmt.println(n,ok) } - + Output: 1234 true 61166 true -**Returns** +**Returns** - value: The parsed integer value - ok: ok=false if a valid integer could not be found, or if the input string contained more than just the number. */ @@ -200,14 +200,14 @@ parse_i64 :: proc{parse_i64_maybe_prefixed, parse_i64_of_base} /* Parses an unsigned 64-bit integer value from the input string without a prefix, using the specified base -**Inputs** +**Inputs** - str: The input string to parse - base: The base of the number system to use for parsing - - Must be between 1 and 16 (inclusive) + - Must be between 1 and 16 (inclusive) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_u64_of_base_example :: proc() { @@ -217,13 +217,13 @@ Example: n, ok = strconv.parse_u64_of_base("5678eee",16) fmt.println(n,ok) } - + Output: 1234 false 90672878 true -**Returns** +**Returns** - value: The parsed uint64 value - ok: A boolean indicating whether the parsing was successful */ @@ -261,15 +261,15 @@ parse_u64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u64, /* Parses an unsigned 64-bit integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - str: The input string to parse - base: The base of the number system to use for parsing (default: 0) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_u64_maybe_prefixed_example :: proc() { @@ -279,13 +279,13 @@ Example: n, ok = strconv.parse_u64_maybe_prefixed("0xee") fmt.println(n,ok) } - + Output: 1234 true 238 true -**Returns** +**Returns** - value: The parsed uint64 value - ok: ok=false if a valid integer could not be found, if the value was negative, or if the input string contained more than just the number. */ @@ -336,14 +336,14 @@ parse_u64 :: proc{parse_u64_maybe_prefixed, parse_u64_of_base} /* Parses a signed integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - s: The input string to parse - base: The base of the number system to use for parsing (default: 0) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string Example: - + import "core:fmt" import "core:strconv" parse_int_example :: proc() { @@ -356,14 +356,14 @@ Example: n, ok = strconv.parse_int("0xffff") // with prefix and inferred base fmt.println(n,ok) } - + Output: 1234 true 65535 true 65535 true -**Returns** +**Returns** - value: The parsed int value - ok: `false` if no appropriate value could be found, or if the input string contained more than just the number. */ @@ -379,11 +379,11 @@ parse_int :: proc(s: string, base := 0, n: ^int = nil) -> (value: int, ok: bool) /* Parses an unsigned integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - s: The input string to parse - base: The base of the number system to use for parsing (default: 0, inferred) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string Example: @@ -1729,7 +1729,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string { } } - if buf == nil { + if buf == nil || r < 0 { return "" } diff --git a/core/strings/builder.odin b/core/strings/builder.odin index 6bb0b2018..97a615990 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -516,7 +516,7 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) { } r = b.buf[len(b.buf)-1] - d := cast(^runtime.Raw_Dynamic_Array)&b.buf + d := (^runtime.Raw_Dynamic_Array)(&b.buf) d.len = max(d.len-1, 0) return } @@ -536,7 +536,7 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) { } r, width = utf8.decode_last_rune(b.buf[:]) - d := cast(^runtime.Raw_Dynamic_Array)&b.buf + d := (^runtime.Raw_Dynamic_Array)(&b.buf) d.len = max(d.len-width, 0) return } diff --git a/core/strings/strings.odin b/core/strings/strings.odin index e9b50bab0..b69c4a0e0 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1,6 +1,8 @@ // Procedures to manipulate UTF-8 encoded strings package strings +import "base:intrinsics" +import "core:bytes" import "core:io" import "core:mem" import "core:unicode" @@ -344,6 +346,17 @@ Output: contains_any :: proc(s, chars: string) -> (res: bool) { return index_any(s, chars) >= 0 } + + +contains_space :: proc(s: string) -> (res: bool) { + for c in s { + if is_space(c) { + return true + } + } + return false +} + /* Returns the UTF-8 rune count of the string `s` @@ -697,6 +710,63 @@ The concatenated string, and an error if allocation fails concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { return concatenate(a, allocator) } + +/* +Returns a substring of the input string `s` with the specified rune offset and length + +Inputs: +- s: The input string to cut +- rune_offset: The starting rune index (default is 0). In runes, not bytes. +- rune_length: The number of runes to include in the substring (default is 0, which returns the remainder of the string). In runes, not bytes. + +Returns: +- res: The substring + +Example: + + import "core:fmt" + import "core:strings" + + cut_example :: proc() { + fmt.println(strings.cut("some example text", 0, 4)) // -> "some" + fmt.println(strings.cut("some example text", 2, 2)) // -> "me" + fmt.println(strings.cut("some example text", 5, 7)) // -> "example" + } + +Output: + + some + me + example + +*/ +cut :: proc(s: string, rune_offset := int(0), rune_length := int(0)) -> (res: string) { + s := s; rune_length := rune_length + + count := 0 + for _, offset in s { + if count == rune_offset { + s = s[offset:] + break + } + count += 1 + } + + if rune_length <= 1 { + return s + } + + count = 0 + for _, offset in s { + if count == rune_length { + s = s[:offset] + break + } + count += 1 + } + return s +} + /* Returns a substring of the input string `s` with the specified rune offset and length @@ -718,9 +788,9 @@ Example: import "core:strings" cut_example :: proc() { - fmt.println(strings.cut("some example text", 0, 4)) // -> "some" - fmt.println(strings.cut("some example text", 2, 2)) // -> "me" - fmt.println(strings.cut("some example text", 5, 7)) // -> "example" + fmt.println(strings.cut_clone("some example text", 0, 4)) // -> "some" + fmt.println(strings.cut_clone("some example text", 2, 2)) // -> "me" + fmt.println(strings.cut_clone("some example text", 5, 7)) // -> "example" } Output: @@ -730,57 +800,11 @@ Output: example */ -cut :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { - s := s; rune_length := rune_length - context.allocator = allocator - - // If we signal that we want the entire remainder (length <= 0) *and* - // the offset is zero, then we can early out by cloning the input - if rune_offset == 0 && rune_length <= 0 { - return clone(s) - } - - // We need to know if we have enough runes to cover offset + length. - rune_count := utf8.rune_count_in_string(s) - - // We're asking for a substring starting after the end of the input string. - // That's just an empty string. - if rune_offset >= rune_count { - return "", nil - } - - // If we don't specify the length of the substring, use the remainder. - if rune_length <= 0 { - rune_length = rune_count - rune_offset - } - - // We don't yet know how many bytes we need exactly. - // But we do know it's bounded by the number of runes * 4 bytes, - // and can be no more than the size of the input string. - bytes_needed := min(rune_length * 4, len(s)) - buf := make([]u8, bytes_needed, allocator, loc) or_return - - byte_offset := 0 - for i := 0; i < rune_count; i += 1 { - _, w := utf8.decode_rune_in_string(s) - - // If the rune is part of the substring, copy it to the output buffer. - if i >= rune_offset { - for j := 0; j < w; j += 1 { - buf[byte_offset+j] = s[j] - } - byte_offset += w - } - - // We're done if we reach the end of the input string, *or* - // if we've reached a specified length in runes. - if rune_length > 0 { - if i == rune_offset + rune_length - 1 { break } - } - s = s[w:] - } - return string(buf[:byte_offset]), nil +cut_clone :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { + res = cut(s, rune_offset, rune_length) + return clone(res, allocator, loc) } + /* Splits the input string `s` into a slice of substrings separated by the specified `sep` string @@ -1424,12 +1448,7 @@ Output: */ index_byte :: proc(s: string, c: byte) -> (res: int) { - for i := 0; i < len(s); i += 1 { - if s[i] == c { - return i - } - } - return -1 + return #force_inline bytes.index_byte(transmute([]u8)s, c) } /* Returns the byte offset of the last byte `c` in the string `s`, -1 when not found. @@ -1464,12 +1483,7 @@ Output: */ last_index_byte :: proc(s: string, c: byte) -> (res: int) { - for i := len(s)-1; i >= 0; i -= 1 { - if s[i] == c { - return i - } - } - return -1 + return #force_inline bytes.last_index_byte(transmute([]u8)s, c) } /* Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found. @@ -2071,7 +2085,10 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator, loc } - t := make([]byte, len(s) + byte_count*(len(new) - len(old)), allocator, loc) + t, err := make([]byte, len(s) + byte_count*(len(new) - len(old)), allocator, loc) + if err != nil { + return + } was_allocation = true w := 0 @@ -3310,3 +3327,106 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator, loc : return costs[n], nil } + +@(private) +internal_substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) { + sub = s + ok = true + + rune_i: int + + if rune_start > 0 { + ok = false + for _, i in sub { + if rune_start == rune_i { + ok = true + sub = sub[i:] + break + } + rune_i += 1 + } + if !ok { return } + } + + if rune_end >= rune_start { + ok = false + for _, i in sub { + if rune_end == rune_i { + ok = true + sub = sub[:i] + break + } + rune_i += 1 + } + + if rune_end == rune_i { + ok = true + } + } + + return +} + +/* +Returns a substring of `s` that starts at rune index `rune_start` and goes up to `rune_end`. + +Think of it as slicing `s[rune_start:rune_end]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_start: the start (inclusive) rune +- rune_end: the end (exclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) { + if rune_start < 0 || rune_end < 0 || rune_end < rune_start { + return + } + + return internal_substring(s, rune_start, rune_end) +} + +/* +Returns a substring of `s` that starts at rune index `rune_start` and goes up to the end of the string. + +Think of it as slicing `s[rune_start:]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_start: the start (inclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring_from :: proc(s: string, rune_start: int) -> (sub: string, ok: bool) { + if rune_start < 0 { + return + } + + return internal_substring(s, rune_start, -1) +} + +/* +Returns a substring of `s` that goes up to rune index `rune_end`. + +Think of it as slicing `s[:rune_end]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_end: the end (exclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring_to :: proc(s: string, rune_end: int) -> (sub: string, ok: bool) { + if rune_end < 0 { + return + } + + return internal_substring(s, -1, rune_end) +} diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin index 0c98124de..53a3bff4b 100644 --- a/core/sync/chan/chan.odin +++ b/core/sync/chan/chan.odin @@ -421,21 +421,20 @@ raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) { @(require_results) can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool { + sync.guard(&c.mutex) if is_buffered(c) { return len(c) > 0 } - sync.guard(&c.mutex) return sync.atomic_load(&c.w_waiting) > 0 } @(require_results) can_send :: proc "contextless" (c: ^Raw_Chan) -> bool { - if is_buffered(c) { - sync.guard(&c.mutex) - return len(c) < cap(c) - } sync.guard(&c.mutex) + if is_buffered(c) { + return c.queue.len < c.queue.cap + } return sync.atomic_load(&c.r_waiting) > 0 } diff --git a/core/sync/doc.odin b/core/sync/doc.odin index 9876c46fb..320732ea7 100644 --- a/core/sync/doc.odin +++ b/core/sync/doc.odin @@ -7,8 +7,8 @@ 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 +- 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 @@ -18,4 +18,4 @@ 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 +package sync diff --git a/core/sync/futex_freebsd.odin b/core/sync/futex_freebsd.odin index 60b1d6e0d..ac6e2400a 100644 --- a/core/sync/futex_freebsd.odin +++ b/core/sync/futex_freebsd.odin @@ -3,35 +3,27 @@ package sync import "core:c" +import "core:sys/freebsd" import "core:time" -UMTX_OP_WAIT :: 2 -UMTX_OP_WAKE :: 3 - -ETIMEDOUT :: 60 - -foreign import libc "system:c" - -foreign libc { - _umtx_op :: proc "c" (obj: rawptr, op: c.int, val: c.ulong, uaddr: rawptr, uaddr2: rawptr) -> c.int --- - __error :: proc "c" () -> ^c.int --- -} - _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { - timeout := [2]i64{14400, 0} // 4 hours - for { - res := _umtx_op(f, UMTX_OP_WAIT, c.ulong(expected), nil, &timeout) + timeout := freebsd.timespec {14400, 0} // 4 hours + timeout_size := cast(rawptr)cast(uintptr)size_of(timeout) - if res != -1 { + for { + errno := freebsd._umtx_op(f, .WAIT_UINT, cast(c.ulong)expected, timeout_size, &timeout) + + if errno == nil { return true } - if __error()^ == ETIMEDOUT { + if errno == .ETIMEDOUT { continue } _panic("_futex_wait failure") } + unreachable() } @@ -40,14 +32,15 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati return false } - timeout := [2]i64{i64(duration/1e9), i64(duration%1e9)} + timeout := freebsd.timespec {cast(freebsd.time_t)duration / 1e9, cast(c.long)duration % 1e9} + timeout_size := cast(rawptr)cast(uintptr)size_of(timeout) - res := _umtx_op(f, UMTX_OP_WAIT, c.ulong(expected), nil, &timeout) - if res != -1 { + errno := freebsd._umtx_op(f, .WAIT_UINT, cast(c.ulong)expected, timeout_size, &timeout) + if errno == nil { return true } - if __error()^ == ETIMEDOUT { + if errno == .ETIMEDOUT { return false } @@ -55,17 +48,17 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati } _futex_signal :: proc "contextless" (f: ^Futex) { - res := _umtx_op(f, UMTX_OP_WAKE, 1, nil, nil) + errno := freebsd._umtx_op(f, .WAKE, 1, nil, nil) - if res == -1 { + if errno != nil { _panic("_futex_signal failure") } } _futex_broadcast :: proc "contextless" (f: ^Futex) { - res := _umtx_op(f, UMTX_OP_WAKE, c.ulong(max(i32)), nil, nil) + errno := freebsd._umtx_op(f, .WAKE, cast(c.ulong)max(i32), nil, nil) - if res == -1 { + if errno != nil { _panic("_futex_broadcast failure") } } diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index 2cf25ac11..1d8e423db 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -338,7 +338,7 @@ atomic_sema_wait :: proc "contextless" (s: ^Atomic_Sema) { original_count := atomic_load_explicit(&s.count, .Relaxed) for original_count == 0 { futex_wait(&s.count, u32(original_count)) - original_count = s.count + original_count = atomic_load_explicit(&s.count, .Relaxed) } if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { return diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin index 6ad3c5bfc..24485a494 100644 --- a/core/sys/darwin/CoreFoundation/CFString.odin +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -1,7 +1,5 @@ package CoreFoundation -import "base:runtime" - foreign import CoreFoundation "system:CoreFoundation.framework" String :: distinct TypeRef // same as CFStringRef @@ -9,157 +7,157 @@ String :: distinct TypeRef // same as CFStringRef StringEncoding :: distinct u32 StringBuiltInEncodings :: enum StringEncoding { - MacRoman = 0, + MacRoman = 0, WindowsLatin1 = 0x0500, - ISOLatin1 = 0x0201, + ISOLatin1 = 0x0201, NextStepLatin = 0x0B01, - ASCII = 0x0600, - Unicode = 0x0100, - UTF8 = 0x08000100, + ASCII = 0x0600, + Unicode = 0x0100, + UTF8 = 0x08000100, NonLossyASCII = 0x0BFF, - UTF16 = 0x0100, + UTF16 = 0x0100, UTF16BE = 0x10000100, UTF16LE = 0x14000100, - UTF32 = 0x0c000100, - UTF32BE = 0x18000100, - UTF32LE = 0x1c000100, + UTF32 = 0x0c000100, + UTF32BE = 0x18000100, + UTF32LE = 0x1c000100, } StringEncodings :: enum Index { - MacJapanese = 1, - MacChineseTrad = 2, - MacKorean = 3, - MacArabic = 4, - MacHebrew = 5, - MacGreek = 6, - MacCyrillic = 7, - MacDevanagari = 9, - MacGurmukhi = 10, - MacGujarati = 11, - MacOriya = 12, - MacBengali = 13, - MacTamil = 14, - MacTelugu = 15, - MacKannada = 16, - MacMalayalam = 17, - MacSinhalese = 18, - MacBurmese = 19, - MacKhmer = 20, - MacThai = 21, - MacLaotian = 22, - MacGeorgian = 23, - MacArmenian = 24, - MacChineseSimp = 25, - MacTibetan = 26, - MacMongolian = 27, - MacEthiopic = 28, - MacCentralEurRoman = 29, - MacVietnamese = 30, - MacExtArabic = 31, - MacSymbol = 33, - MacDingbats = 34, - MacTurkish = 35, - MacCroatian = 36, - MacIcelandic = 37, - MacRomanian = 38, - MacCeltic = 39, - MacGaelic = 40, - MacFarsi = 0x8C, - MacUkrainian = 0x98, - MacInuit = 0xEC, - MacVT100 = 0xFC, - MacHFS = 0xFF, - ISOLatin2 = 0x0202, - ISOLatin3 = 0x0203, - ISOLatin4 = 0x0204, - ISOLatinCyrillic = 0x0205, - ISOLatinArabic = 0x0206, - ISOLatinGreek = 0x0207, - ISOLatinHebrew = 0x0208, - ISOLatin5 = 0x0209, - ISOLatin6 = 0x020A, - ISOLatinThai = 0x020B, - ISOLatin7 = 0x020D, - ISOLatin8 = 0x020E, - ISOLatin9 = 0x020F, - ISOLatin10 = 0x0210, - DOSLatinUS = 0x0400, - DOSGreek = 0x0405, - DOSBalticRim = 0x0406, - DOSLatin1 = 0x0410, - DOSGreek1 = 0x0411, - DOSLatin2 = 0x0412, - DOSCyrillic = 0x0413, - DOSTurkish = 0x0414, - DOSPortuguese = 0x0415, - DOSIcelandic = 0x0416, - DOSHebrew = 0x0417, - DOSCanadianFrench = 0x0418, - DOSArabic = 0x0419, - DOSNordic = 0x041A, - DOSRussian = 0x041B, - DOSGreek2 = 0x041C, - DOSThai = 0x041D, - DOSJapanese = 0x0420, - DOSChineseSimplif = 0x0421, - DOSKorean = 0x0422, - DOSChineseTrad = 0x0423, - WindowsLatin2 = 0x0501, - WindowsCyrillic = 0x0502, - WindowsGreek = 0x0503, - WindowsLatin5 = 0x0504, - WindowsHebrew = 0x0505, - WindowsArabic = 0x0506, - WindowsBalticRim = 0x0507, - WindowsVietnamese = 0x0508, - WindowsKoreanJohab = 0x0510, - ANSEL = 0x0601, - JIS_X0201_76 = 0x0620, - JIS_X0208_83 = 0x0621, - JIS_X0208_90 = 0x0622, - JIS_X0212_90 = 0x0623, - JIS_C6226_78 = 0x0624, - ShiftJIS_X0213 = 0x0628, - ShiftJIS_X0213_MenKuTen = 0x0629, - GB_2312_80 = 0x0630, - GBK_95 = 0x0631, - GB_18030_2000 = 0x0632, - KSC_5601_87 = 0x0640, - KSC_5601_92_Johab = 0x0641, - CNS_11643_92_P1 = 0x0651, - CNS_11643_92_P2 = 0x0652, - CNS_11643_92_P3 = 0x0653, - ISO_2022_JP = 0x0820, - ISO_2022_JP_2 = 0x0821, - ISO_2022_JP_1 = 0x0822, - ISO_2022_JP_3 = 0x0823, - ISO_2022_CN = 0x0830, - ISO_2022_CN_EXT = 0x0831, - ISO_2022_KR = 0x0840, - EUC_JP = 0x0920, - EUC_CN = 0x0930, - EUC_TW = 0x0931, - EUC_KR = 0x0940, - ShiftJIS = 0x0A01, - KOI8_R = 0x0A02, - Big5 = 0x0A03, - MacRomanLatin1 = 0x0A04, - HZ_GB_2312 = 0x0A05, - Big5_HKSCS_1999 = 0x0A06, - VISCII = 0x0A07, - KOI8_U = 0x0A08, - Big5_E = 0x0A09, - NextStepJapanese = 0x0B02, - EBCDIC_US = 0x0C01, - EBCDIC_CP037 = 0x0C02, - UTF7 = 0x04000100, - UTF7_IMAP = 0x0A10, - ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead. + MacJapanese = 1, + MacChineseTrad = 2, + MacKorean = 3, + MacArabic = 4, + MacHebrew = 5, + MacGreek = 6, + MacCyrillic = 7, + MacDevanagari = 9, + MacGurmukhi = 10, + MacGujarati = 11, + MacOriya = 12, + MacBengali = 13, + MacTamil = 14, + MacTelugu = 15, + MacKannada = 16, + MacMalayalam = 17, + MacSinhalese = 18, + MacBurmese = 19, + MacKhmer = 20, + MacThai = 21, + MacLaotian = 22, + MacGeorgian = 23, + MacArmenian = 24, + MacChineseSimp = 25, + MacTibetan = 26, + MacMongolian = 27, + MacEthiopic = 28, + MacCentralEurRoman = 29, + MacVietnamese = 30, + MacExtArabic = 31, + MacSymbol = 33, + MacDingbats = 34, + MacTurkish = 35, + MacCroatian = 36, + MacIcelandic = 37, + MacRomanian = 38, + MacCeltic = 39, + MacGaelic = 40, + MacFarsi = 0x8C, + MacUkrainian = 0x98, + MacInuit = 0xEC, + MacVT100 = 0xFC, + MacHFS = 0xFF, + ISOLatin2 = 0x0202, + ISOLatin3 = 0x0203, + ISOLatin4 = 0x0204, + ISOLatinCyrillic = 0x0205, + ISOLatinArabic = 0x0206, + ISOLatinGreek = 0x0207, + ISOLatinHebrew = 0x0208, + ISOLatin5 = 0x0209, + ISOLatin6 = 0x020A, + ISOLatinThai = 0x020B, + ISOLatin7 = 0x020D, + ISOLatin8 = 0x020E, + ISOLatin9 = 0x020F, + ISOLatin10 = 0x0210, + DOSLatinUS = 0x0400, + DOSGreek = 0x0405, + DOSBalticRim = 0x0406, + DOSLatin1 = 0x0410, + DOSGreek1 = 0x0411, + DOSLatin2 = 0x0412, + DOSCyrillic = 0x0413, + DOSTurkish = 0x0414, + DOSPortuguese = 0x0415, + DOSIcelandic = 0x0416, + DOSHebrew = 0x0417, + DOSCanadianFrench = 0x0418, + DOSArabic = 0x0419, + DOSNordic = 0x041A, + DOSRussian = 0x041B, + DOSGreek2 = 0x041C, + DOSThai = 0x041D, + DOSJapanese = 0x0420, + DOSChineseSimplif = 0x0421, + DOSKorean = 0x0422, + DOSChineseTrad = 0x0423, + WindowsLatin2 = 0x0501, + WindowsCyrillic = 0x0502, + WindowsGreek = 0x0503, + WindowsLatin5 = 0x0504, + WindowsHebrew = 0x0505, + WindowsArabic = 0x0506, + WindowsBalticRim = 0x0507, + WindowsVietnamese = 0x0508, + WindowsKoreanJohab = 0x0510, + ANSEL = 0x0601, + JIS_X0201_76 = 0x0620, + JIS_X0208_83 = 0x0621, + JIS_X0208_90 = 0x0622, + JIS_X0212_90 = 0x0623, + JIS_C6226_78 = 0x0624, + ShiftJIS_X0213 = 0x0628, + ShiftJIS_X0213_MenKuTen = 0x0629, + GB_2312_80 = 0x0630, + GBK_95 = 0x0631, + GB_18030_2000 = 0x0632, + KSC_5601_87 = 0x0640, + KSC_5601_92_Johab = 0x0641, + CNS_11643_92_P1 = 0x0651, + CNS_11643_92_P2 = 0x0652, + CNS_11643_92_P3 = 0x0653, + ISO_2022_JP = 0x0820, + ISO_2022_JP_2 = 0x0821, + ISO_2022_JP_1 = 0x0822, + ISO_2022_JP_3 = 0x0823, + ISO_2022_CN = 0x0830, + ISO_2022_CN_EXT = 0x0831, + ISO_2022_KR = 0x0840, + EUC_JP = 0x0920, + EUC_CN = 0x0930, + EUC_TW = 0x0931, + EUC_KR = 0x0940, + ShiftJIS = 0x0A01, + KOI8_R = 0x0A02, + Big5 = 0x0A03, + MacRomanLatin1 = 0x0A04, + HZ_GB_2312 = 0x0A05, + Big5_HKSCS_1999 = 0x0A06, + VISCII = 0x0A07, + KOI8_U = 0x0A08, + Big5_E = 0x0A09, + NextStepJapanese = 0x0B02, + EBCDIC_US = 0x0C01, + EBCDIC_CP037 = 0x0C02, + UTF7 = 0x04000100, + UTF7_IMAP = 0x0A10, + ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead. } -@(link_prefix = "CF", default_calling_convention = "c") +@(link_prefix="CF", default_calling_convention="c") foreign CoreFoundation { // Copies the character contents of a string to a local C string buffer after converting the characters to a given encoding. StringGetCString :: proc(theString: String, buffer: [^]byte, bufferSize: Index, encoding: StringEncoding) -> b8 --- @@ -181,23 +179,16 @@ foreign CoreFoundation { STR :: StringMakeConstantString -StringCopyToOdinString :: proc( - theString: String, - allocator := context.allocator, -) -> ( - str: string, - ok: bool, -) #optional_ok { +StringCopyToOdinString :: proc(theString: String, allocator := context.allocator) -> (str: string, ok: bool) #optional_ok { length := StringGetLength(theString) max := StringGetMaximumSizeForEncoding(length, StringEncoding(StringBuiltInEncodings.UTF8)) buf, err := make([]byte, max, allocator) - if err != nil { return } - - raw_str := runtime.Raw_String { - data = raw_data(buf), + if err != nil { + return } - StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), max, (^Index)(&raw_str.len)) - return transmute(string)raw_str, true + n: Index + StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), Index(len(buf)), &n) + return string(buf[:n]), true } diff --git a/core/sys/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin index 34221aed6..7191f6d07 100644 --- a/core/sys/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -79,7 +79,10 @@ Application_setActivationPolicy :: proc "c" (self: ^Application, activationPolic return msgSend(BOOL, self, "setActivationPolicy:", activationPolicy) } -@(deprecated="Use NSApplication method activate instead.") +// NOTE: this is technically deprecated but still actively used (Sokol, glfw, SDL, etc.) +// and has no clear alternative although `activate` is what Apple tells you to use, +// that does not work the same way. +// @(deprecated="Use NSApplication method activate instead.") @(objc_type=Application, objc_name="activateIgnoringOtherApps") Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) { msgSend(nil, self, "activateIgnoringOtherApps:", ignoreOtherApps) @@ -95,6 +98,11 @@ Application_setTitle :: proc "c" (self: ^Application, title: ^String) { msgSend(nil, self, "setTitle", title) } +@(objc_type=Application, objc_name="mainMenu") +Window_mainMenu :: proc "c" (self: ^Application) -> ^Menu { + return msgSend(^Menu, self, "mainMenu") +} + @(objc_type=Application, objc_name="setMainMenu") Application_setMainMenu :: proc "c" (self: ^Application, menu: ^Menu) { msgSend(nil, self, "setMainMenu:", menu) @@ -110,6 +118,11 @@ Application_run :: proc "c" (self: ^Application) { msgSend(nil, self, "run") } +@(objc_type=Application, objc_name="finishLaunching") +Application_finishLaunching :: proc "c" (self: ^Application) { + msgSend(nil, self, "finishLaunching") +} + @(objc_type=Application, objc_name="terminate") Application_terminate :: proc "c" (self: ^Application, sender: ^Object) { msgSend(nil, self, "terminate:", sender) diff --git a/core/sys/darwin/Foundation/NSDate.odin b/core/sys/darwin/Foundation/NSDate.odin index f8096c698..41efb0cf5 100644 --- a/core/sys/darwin/Foundation/NSDate.odin +++ b/core/sys/darwin/Foundation/NSDate.odin @@ -16,4 +16,14 @@ Date_init :: proc "c" (self: ^Date) -> ^Date { @(objc_type=Date, objc_name="dateWithTimeIntervalSinceNow") Date_dateWithTimeIntervalSinceNow :: proc "c" (secs: TimeInterval) -> ^Date { return msgSend(^Date, Date, "dateWithTimeIntervalSinceNow:", secs) -} \ No newline at end of file +} + +@(objc_type=Date, objc_name="distantFuture", objc_is_class_method=true) +Date_distantFuture :: proc "c" () -> ^Date { + return msgSend(^Date, Date, "distantFuture") +} + +@(objc_type=Date, objc_name="distantPast", objc_is_class_method=true) +Date_distantPast :: proc "c" () -> ^Date { + return msgSend(^Date, Date, "distantPast") +} diff --git a/core/sys/darwin/Foundation/NSEvent.odin b/core/sys/darwin/Foundation/NSEvent.odin index b9f247230..f20afd3ab 100644 --- a/core/sys/darwin/Foundation/NSEvent.odin +++ b/core/sys/darwin/Foundation/NSEvent.odin @@ -105,6 +105,28 @@ PointingDeviceType :: enum UInteger { Eraser = 3, } +EventModifierFlag :: enum UInteger { + CapsLock = 16, + Shift = 17, + Control = 18, + Option = 19, + Command = 20, + NumericPad = 21, + Help = 22, + Function = 23, +} + +EventModifierFlags :: distinct bit_set[EventModifierFlag; UInteger] +EventModifierFlagCapsLock :: EventModifierFlags{.CapsLock} +EventModifierFlagShift :: EventModifierFlags{.Shift} +EventModifierFlagControl :: EventModifierFlags{.Control} +EventModifierFlagOption :: EventModifierFlags{.Option} +EventModifierFlagCommand :: EventModifierFlags{.Command} +EventModifierFlagNumericPad :: EventModifierFlags{.NumericPad} +EventModifierFlagHelp :: EventModifierFlags{.Help} +EventModifierFlagFunction :: EventModifierFlags{.Function} +EventModifierFlagDeviceIndependentFlagsMask : UInteger : 0xffff0000 + // Defined in Carbon.framework Events.h kVK :: enum { ANSI_A = 0x00, @@ -236,8 +258,8 @@ Event_type :: proc "c" (self: ^Event) -> EventType { return msgSend(EventType, self, "type") } @(objc_type=Event, objc_name="modifierFlags") -Event_modifierFlags :: proc "c" (self: ^Event) -> UInteger { - return msgSend(UInteger, self, "modifierFlags") +Event_modifierFlags :: proc "c" (self: ^Event) -> EventModifierFlags { + return msgSend(EventModifierFlags, self, "modifierFlags") } @(objc_type=Event, objc_name="timestamp") Event_timestamp :: proc "c" (self: ^Event) -> TimeInterval { diff --git a/core/sys/darwin/Foundation/NSMenu.odin b/core/sys/darwin/Foundation/NSMenu.odin index 79da36601..e49162a7f 100644 --- a/core/sys/darwin/Foundation/NSMenu.odin +++ b/core/sys/darwin/Foundation/NSMenu.odin @@ -24,7 +24,7 @@ MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object) @(objc_class="NSMenuItem") -MenuItem :: struct {using _: Object} +MenuItem :: struct {using _: Object} @(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true) MenuItem_alloc :: proc "c" () -> ^MenuItem { @@ -70,11 +70,15 @@ MenuItem_setSubmenu :: proc "c" (self: ^MenuItem, submenu: ^Menu) { msgSend(nil, self, "setSubmenu:", submenu) } +@(objc_type=MenuItem, objc_name="title") +MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "title") +} @(objc_class="NSMenu") -Menu :: struct {using _: Object} +Menu :: struct {using _: Object} @(objc_type=Menu, objc_name="alloc", objc_is_class_method=true) Menu_alloc :: proc "c" () -> ^Menu { @@ -100,4 +104,9 @@ Menu_addItem :: proc "c" (self: ^Menu, item: ^MenuItem) { @(objc_type=Menu, objc_name="addItemWithTitle") Menu_addItemWithTitle :: proc "c" (self: ^Menu, title: ^String, selector: SEL, keyEquivalent: ^String) -> ^MenuItem { return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", title, selector, keyEquivalent) +} + +@(objc_type=Menu, objc_name="itemArray") +Menu_itemArray :: proc "c" (self: ^Menu) -> ^Array { + return msgSend(^Array, self, "itemArray") } \ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSOpenPanel.odin b/core/sys/darwin/Foundation/NSOpenPanel.odin index ac5f9674e..482347daa 100644 --- a/core/sys/darwin/Foundation/NSOpenPanel.odin +++ b/core/sys/darwin/Foundation/NSOpenPanel.odin @@ -29,3 +29,7 @@ OpenPanel_setResolvesAliases :: proc "c" (self: ^OpenPanel, setting: BOOL) { OpenPanel_setAllowsMultipleSelection :: proc "c" (self: ^OpenPanel, setting: BOOL) { msgSend(nil, self, "setAllowsMultipleSelection:", setting) } +@(objc_type=OpenPanel, objc_name="setAllowedFileTypes") +OpenPanel_setAllowedFileTypes :: proc "c" (self: ^OpenPanel, types: ^Array) { + msgSend(nil, self, "setAllowedFileTypes:", types) +} diff --git a/core/sys/darwin/Foundation/NSScreen.odin b/core/sys/darwin/Foundation/NSScreen.odin index a8fe44aa5..79ab00fbe 100644 --- a/core/sys/darwin/Foundation/NSScreen.odin +++ b/core/sys/darwin/Foundation/NSScreen.odin @@ -31,3 +31,7 @@ Screen_visibleFrame :: proc "c" (self: ^Screen) -> Rect { Screen_colorSpace :: proc "c" (self: ^Screen) -> ^ColorSpace { return msgSend(^ColorSpace, self, "colorSpace") } +@(objc_type=Screen, objc_name="backingScaleFactor") +Screen_backingScaleFactor :: proc "c" (self: ^Screen) -> Float { + return msgSend(Float, self, "backingScaleFactor") +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSString.odin b/core/sys/darwin/Foundation/NSString.odin index b4918b3fb..a10b33fc0 100644 --- a/core/sys/darwin/Foundation/NSString.odin +++ b/core/sys/darwin/Foundation/NSString.odin @@ -58,7 +58,10 @@ MakeConstantString :: proc "c" (#const c: cstring) -> ^String { @(link_prefix="NS", default_calling_convention="c") foreign Foundation { - StringFromClass :: proc(cls: Class) -> ^String --- + StringFromClass :: proc(cls: Class) -> ^String --- + ClassFromString :: proc(str: ^String) -> Class --- + StringFromSelector :: proc(selector: SEL) -> ^String --- + SelectorFromString :: proc(str: ^String) -> SEL --- } @(objc_type=String, objc_name="alloc", objc_is_class_method=true) diff --git a/core/sys/darwin/Foundation/NSWindow.odin b/core/sys/darwin/Foundation/NSWindow.odin index e6103a58a..0fe334207 100644 --- a/core/sys/darwin/Foundation/NSWindow.odin +++ b/core/sys/darwin/Foundation/NSWindow.odin @@ -627,18 +627,7 @@ Window_alloc :: proc "c" () -> ^Window { @(objc_type=Window, objc_name="initWithContentRect") Window_initWithContentRect :: proc (self: ^Window, contentRect: Rect, styleMask: WindowStyleMask, backing: BackingStoreType, doDefer: BOOL) -> ^Window { - self := self - // HACK: due to a compiler bug, the generated calling code does not - // currently work for this message. Has to do with passing a struct along - // with other parameters, so we don't send the rect here. - // Omiting the rect argument here actually works, because of how the C - // calling conventions are defined. - self = msgSend(^Window, self, "initWithContentRect:styleMask:backing:defer:", styleMask, backing, doDefer) - - // apply the contentRect now, since we did not pass it to the init call - msgSend(nil, self, "setContentSize:", contentRect.size) - msgSend(nil, self, "setFrameOrigin:", contentRect.origin) - return self + return msgSend(^Window, self, "initWithContentRect:styleMask:backing:defer:", contentRect, styleMask, backing, doDefer) } @(objc_type=Window, objc_name="contentView") Window_contentView :: proc "c" (self: ^Window) -> ^View { @@ -716,3 +705,47 @@ Window_backingScaleFactor :: proc "c" (self: ^Window) -> Float { Window_setWantsLayer :: proc "c" (self: ^Window, ok: BOOL) { msgSend(nil, self, "setWantsLayer:", ok) } +@(objc_type=Window, objc_name="setIsMiniaturized") +Window_setIsMiniaturized :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setIsMiniaturized:", ok) +} +@(objc_type=Window, objc_name="setIsVisible") +Window_setIsVisible :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setIsVisible:", ok) +} +@(objc_type=Window, objc_name="setIsZoomed") +Window_setIsZoomed :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setIsZoomed:", ok) +} +@(objc_type=Window, objc_name="isZoomable") +Window_isZoomable :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isZoomable") +} +@(objc_type=Window, objc_name="isResizable") +Window_isResizable :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isResizable") +} +@(objc_type=Window, objc_name="isModalPanel") +Window_isModalPanel :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isModalPanel") +} +@(objc_type=Window, objc_name="isMiniaturizable") +Window_isMiniaturizable :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isMiniaturizable") +} +@(objc_type=Window, objc_name="isFloatingPanel") +Window_isFloatingPanel :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isFloatingPanel") +} +@(objc_type=Window, objc_name="hasCloseBox") +Window_hasCloseBox :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "hasCloseBox") +} +@(objc_type=Window, objc_name="hasTitleBar") +Window_hasTitleBar :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "hasTitleBar") +} +@(objc_type=Window, objc_name="orderedIndex") +Window_orderedIndex :: proc "c" (self: ^Window) -> Integer { + return msgSend(Integer, self, "orderedIndex") +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/objc.odin b/core/sys/darwin/Foundation/objc.odin index 673996cbe..82d6199ce 100644 --- a/core/sys/darwin/Foundation/objc.odin +++ b/core/sys/darwin/Foundation/objc.odin @@ -9,18 +9,85 @@ import "core:c" IMP :: proc "c" (object: id, sel: SEL, #c_vararg args: ..any) -> id +@(default_calling_convention="c") foreign Foundation { - objc_lookUpClass :: proc "c" (name: cstring) -> Class --- - sel_registerName :: proc "c" (name: cstring) -> SEL --- - objc_allocateClassPair :: proc "c" (superclass : Class, name : cstring, extraBytes : c.size_t) -> Class --- - objc_registerClassPair :: proc "c" (cls : Class) --- + objc_getMetaClass :: proc(name: cstring) -> id --- + objc_lookUpClass :: proc(name: cstring) -> Class --- + objc_allocateClassPair :: proc(superclass: Class, name: cstring, extraBytes: c.size_t) -> Class --- + objc_registerClassPair :: proc(cls: Class) --- + objc_disposeClassPair :: proc(cls: Class) --- + objc_duplicateClass :: proc(original: Class, name: cstring, extraBytes: c.size_t) -> Class --- + objc_getProtocol :: proc(name: cstring) -> ^Protocol --- + objc_copyProtocolList :: proc(outCount: ^uint) -> [^]^Protocol --- + objc_constructInstance :: proc(cls: Class, bytes: rawptr) -> id --- + objc_destructInstance :: proc(obj: id) -> rawptr --- + objc_getClassList :: proc(buffer: [^]Class, bufferCount: int) -> int --- + objc_copyClassList :: proc(outCount: ^uint) -> [^]Class --- + objc_getRequiredClass :: proc(name: cstring) -> Class --- + objc_setAssociatedObject :: proc(object: id, key: rawptr, value: id, policy: objc_AssociationPolicy) --- + objc_getAssociatedObject :: proc(object: id, key: rawptr) -> id --- + objc_removeAssociatedObjects :: proc(object: id) --- - class_addMethod :: proc "c" (cls: Class, name: SEL, imp: IMP, types: cstring) -> BOOL --- - class_getInstanceMethod :: proc "c" (cls: Class, name: SEL) -> Method --- - class_createInstance :: proc "c" (cls: Class, extraBytes: c.size_t) -> id --- + sel_registerName :: proc(name: cstring) -> SEL --- + sel_getName :: proc(sel: SEL) -> cstring --- + sel_isEqual :: proc(lhs, rhs: SEL) -> BOOL --- - method_setImplementation :: proc "c" (method: Method, imp: IMP) --- - object_getIndexedIvars :: proc(obj: id) -> rawptr --- + class_addMethod :: proc(cls: Class, name: SEL, imp: IMP, types: cstring) -> BOOL --- + class_getInstanceMethod :: proc(cls: Class, name: SEL) -> Method --- + class_getClassMethod :: proc(cls: Class, name: SEL) -> Method --- + class_copyMethodList :: proc(cls: Class, outCount: ^uint) -> [^]Method --- + class_createInstance :: proc(cls: Class, extraBytes: c.size_t) -> id --- + class_replaceMethod :: proc(cls: Class, name: SEL, imp: IMP, types: cstring) -> IMP --- + class_getMethodImplementation :: proc(cls: Class, name: SEL) -> IMP --- + class_getSuperclass :: proc(cls: Class) -> Class --- + class_getName :: proc(cls: Class) -> cstring --- + class_isMetaClass :: proc(cls: Class) -> BOOL --- + class_addProtocol :: proc(cls: Class, protocol: ^Protocol) -> BOOL --- + class_getVersion :: proc(cls: Class) -> c.int --- + class_setVersion :: proc(cls: Class, version: c.int) --- + class_getProperty :: proc(cls: Class, name: cstring) -> objc_property_t --- + class_addProperty :: proc(cls: Class, name: cstring, attributes: [^]objc_property_attribute_t, attributeCount: uint) -> BOOL --- + class_replaceProperty :: proc(cls: Class, name: cstring, attributes: [^]objc_property_attribute_t, attributeCount: uint) --- + class_copyPropertyList :: proc(cls: Class, outCount: ^uint) -> [^]objc_property_t --- + class_conformsToProtocol :: proc(cls: Class, protocol: ^Protocol) -> BOOL --- + class_copyProtocolList :: proc(cls: Class, outCount: ^uint) -> [^]^Protocol --- + class_respondsToSelector :: proc(cls: Class, sel: SEL) -> BOOL --- + class_getClassVariable :: proc(cls: Class, name: cstring) -> Ivar --- + class_getInstanceVariable :: proc(cls: Class, name: cstring) -> Ivar --- + class_addIvar :: proc(cls: Class, name: cstring, size: c.size_t, alignment: u8, types: cstring) -> BOOL --- + class_copyIvarList :: proc(cls: Class, outCount: ^uint) -> [^]Ivar --- + class_getInstanceSize :: proc(cls: Class) -> c.size_t --- + + property_getName :: proc(property: objc_property_t) -> cstring --- + property_getAttributes :: proc(property: objc_property_t) -> cstring --- + property_copyAttributeList :: proc(property: objc_property_t, outCount: ^uint) -> [^]objc_property_attribute_t --- + property_copyAttributeValue :: proc(property: objc_property_t, attributeName: cstring) -> cstring --- + + protocol_conformsToProtocol :: proc(proto: ^Protocol, other: ^Protocol) -> BOOL --- + protocol_isEqual :: proc(proto: ^Protocol, other: ^Protocol) -> BOOL --- + protocol_getName :: proc(proto: ^Protocol) -> cstring --- + + method_getImplementation :: proc(m: Method) -> IMP --- + method_setImplementation :: proc(m: Method, imp: IMP) --- + method_copyArgumentType :: proc(m: Method, index: uint) -> cstring --- + method_getReturnType :: proc(m: Method, dst: cstring, dst_len: c.size_t) --- + method_getNumberOfArguments :: proc(m: Method) -> uint --- + method_getArgumentType :: proc(m: Method, index: uint, dst: cstring, dst_len: c.size_t) --- + + object_getClass :: proc(obj: id) -> Class --- + object_setClass :: proc(obj: id, cls: Class) -> Class --- + object_copy :: proc(obj: id, size: c.size_t) -> id --- + object_dispose :: proc(obj: id) -> id --- + object_getClassName :: proc(obj: id) -> cstring --- + object_getIndexedIvars :: proc(obj: id) -> rawptr --- + object_getInstanceVariable :: proc(obj: id, name: cstring, outValue: rawptr) -> Ivar --- + object_setInstanceVariable :: proc(obj: id, name: cstring, value: rawptr) -> Ivar --- + object_getIvar :: proc(obj: id, ivar: Ivar) -> id --- + object_setIvar :: proc(obj: id, ivar: Ivar, value: id) --- + + ivar_getName :: proc(v: Ivar) -> cstring --- + ivar_getTypeEncoding :: proc(v: Ivar) -> cstring --- + ivar_getOffset :: proc(v: Ivar) -> c.ptrdiff_t --- } @@ -41,7 +108,17 @@ objc_method :: struct { } objc_method_list :: struct {} +objc_property :: struct{} +objc_property_t :: ^objc_property + +objc_property_attribute_t :: struct { + name: cstring, + value: cstring, +} + objc_ivar :: struct {} +Ivar :: ^objc_ivar + objc_ivar_list :: struct {} objc_cache :: struct { @@ -72,10 +149,18 @@ objc_class_internals :: struct { info: c.long, instance_size: c.long, ivars: ^objc_ivar_list, - + methodLists: ^^objc_method_list, cache: rawptr, protocols: rawptr, } + +objc_AssociationPolicy :: enum c.uintptr_t { + Assign = 0, + Retain_Nonatomic = 1, + Copy_Nonatomic = 3, + Retain = 01401, + Copy = 01403, +} diff --git a/core/sys/darwin/proc.odin b/core/sys/darwin/proc.odin new file mode 100644 index 000000000..fa5391f6f --- /dev/null +++ b/core/sys/darwin/proc.odin @@ -0,0 +1,192 @@ +package darwin + +import "base:intrinsics" + +import "core:sys/posix" + +foreign import lib "system:System.framework" + +// Incomplete bindings to the proc API on MacOS, add to when needed. + +foreign lib { + proc_pidinfo :: proc(pid: posix.pid_t, flavor: PID_Info_Flavor, arg: i64, buffer: rawptr, buffersize: i32) -> i32 --- + proc_pidpath :: proc(pid: posix.pid_t, buffer: [^]byte, buffersize: u32) -> i32 --- + proc_listallpids :: proc(buffer: [^]i32, buffersize: i32) -> i32 --- + proc_pid_rusage :: proc(pid: posix.pid_t, flavor: Pid_Rusage_Flavor, buffer: rawptr) -> i32 --- +} + +MAXCOMLEN :: 16 + +proc_bsdinfo :: struct { + pbi_flags: PBI_Flags, + pbi_status: u32, + pbi_xstatus: u32, + pbi_pid: u32, + pbi_ppid: u32, + pbi_uid: posix.uid_t, + pbi_gid: posix.gid_t, + pbi_ruid: posix.uid_t, + pbi_rgid: posix.gid_t, + pbi_svuid: posix.uid_t, + pbi_svgid: posix.gid_t, + rfu_1: u32, + pbi_comm: [MAXCOMLEN]byte `fmt:"s,0"`, + pbi_name: [2 * MAXCOMLEN]byte `fmt:"s,0"`, + pbi_nfiles: u32, + pbi_pgid: u32, + pbi_pjobc: u32, + e_tdev: u32, + e_tpgid: u32, + pbi_nice: i32, + pbi_start_tvsec: u64, + pbi_start_tvusec: u64, +} + +proc_bsdshortinfo :: struct { + pbsi_pid: u32, + pbsi_ppid: u32, + pbsi_pgid: u32, + pbsi_status: u32, + pbsi_comm: [MAXCOMLEN]byte `fmt:"s,0"`, + pbsi_flags: PBI_Flags, + pbsi_uid: posix.uid_t, + pbsi_gid: posix.gid_t, + pbsi_ruid: posix.uid_t, + pbsi_rgid: posix.gid_t, + pbsi_svuid: posix.uid_t, + pbsi_svgid: posix.gid_t, + pbsi_rfu: u32, +} + +proc_vnodepathinfo :: struct { + pvi_cdir: vnode_info_path, + pvi_rdir: vnode_info_path, +} + +vnode_info_path :: struct { + vip_vi: vnode_info, + vip_path: [posix.PATH_MAX]byte, +} + +vnode_info :: struct { + vi_stat: vinfo_stat, + vi_type: i32, + vi_pad: i32, + vi_fsid: fsid_t, +} + +vinfo_stat :: struct { + vst_dev: u32, + vst_mode: u16, + vst_nlink: u16, + vst_ino: u64, + vst_uid: posix.uid_t, + vst_gid: posix.gid_t, + vst_atime: i64, + vst_atimensec: i64, + vst_mtime: i64, + vst_mtimensec: i64, + vst_ctime: i64, + vst_ctimensec: i64, + vst_birthtime: i64, + vst_birthtimensec: i64, + vst_size: posix.off_t, + vst_blocks: i64, + vst_blksize: i32, + vst_flags: u32, + vst_gen: u32, + vst_rdev: u32, + vst_qspare: [2]i64, +} + +proc_taskinfo :: struct { + pti_virtual_size: u64 `fmt:"M"`, + pti_resident_size: u64 `fmt:"M"`, + pti_total_user: u64, + pti_total_system: u64, + pti_threads_user: u64, + pti_threads_system: u64, + pti_policy: i32, + pti_faults: i32, + pti_pageins: i32, + pti_cow_faults: i32, + pti_messages_sent: i32, + pti_messages_received: i32, + pti_syscalls_mach: i32, + pti_syscalls_unix: i32, + pti_csw: i32, + pti_threadnum: i32, + pti_numrunning: i32, + pti_priority: i32, +} + +proc_taskallinfo :: struct { + pbsd: proc_bsdinfo, + ptinfo: proc_taskinfo, +} + +fsid_t :: distinct [2]i32 + +PBI_Flag_Bits :: enum u32 { + SYSTEM = intrinsics.constant_log2(0x0001), + TRACED = intrinsics.constant_log2(0x0002), + INEXIT = intrinsics.constant_log2(0x0004), + PWAIT = intrinsics.constant_log2(0x0008), + LP64 = intrinsics.constant_log2(0x0010), + SLEADER = intrinsics.constant_log2(0x0020), + CTTY = intrinsics.constant_log2(0x0040), + CONTROLT = intrinsics.constant_log2(0x0080), + THCWD = intrinsics.constant_log2(0x0100), + PC_THROTTLE = intrinsics.constant_log2(0x0200), + PC_SUSP = intrinsics.constant_log2(0x0400), + PC_KILL = intrinsics.constant_log2(0x0600), + PA_THROTTLE = intrinsics.constant_log2(0x0800), + PA_SUSP = intrinsics.constant_log2(0x1000), + PA_PSUGID = intrinsics.constant_log2(0x2000), + EXEC = intrinsics.constant_log2(0x4000), +} +PBI_Flags :: bit_set[PBI_Flag_Bits; u32] + +PID_Info_Flavor :: enum i32 { + LISTFDS = 1, + TASKALLINFO, + BSDINFO, + TASKINFO, + THREADINFO, + LISTTHREADS, + REGIONINFO, + REGIONPATHINFO, + VNODEPATHINFO, + THREADPATHINFO, + PATHINFO, + WORKQUEUEINFO, + SHORTBSDINFO, + LISTFILEPORTS, + THREADID64INFO, + RUSAGE, +} + +PIDPATHINFO_MAXSIZE :: 4*posix.PATH_MAX + +Pid_Rusage_Flavor :: enum i32 { + V0, + V1, + V2, + V3, + V4, + V5, +} + +rusage_info_v0 :: struct { + ri_uuid: [16]u8, + ri_user_time: u64, + ri_system_time: u64, + ri_pkg_idle_wkups: u64, + ri_interrupt_wkups: u64, + ri_pageins: u64, + ri_wired_size: u64, + ri_resident_size: u64, + ri_phys_footprint: u64, + ri_proc_start_abstime: u64, + ri_proc_exit_abstime: u64, +} diff --git a/core/sys/darwin/xnu_system_call_helpers.odin b/core/sys/darwin/xnu_system_call_helpers.odin index 7fa59bfe0..ae8373f99 100644 --- a/core/sys/darwin/xnu_system_call_helpers.odin +++ b/core/sys/darwin/xnu_system_call_helpers.odin @@ -15,9 +15,9 @@ sys_write_string :: proc (fd: c.int, message: string) -> bool { Offset_From :: enum c.int { SEEK_SET = 0, // the offset is set to offset bytes. SEEK_CUR = 1, // the offset is set to its current location plus offset bytes. - SEEK_END = 2, // the offset is set to the size of the file plus offset bytes. - SEEK_HOLE = 3, // the offset is set to the start of the next hole greater than or equal to the supplied offset. - SEEK_DATA = 4, // the offset is set to the start of the next non-hole file region greater than or equal to the supplied offset. + SEEK_END = 2, // the offset is set to the size of the file plus offset bytes. + SEEK_HOLE = 3, // the offset is set to the start of the next hole greater than or equal to the supplied offset. + SEEK_DATA = 4, // the offset is set to the start of the next non-hole file region greater than or equal to the supplied offset. } Open_Flags_Enum :: enum u8 { @@ -91,6 +91,29 @@ _sys_permission_mode :: #force_inline proc (mode: Permission) -> u32 { return cflags } +_sys_open_mode :: #force_inline proc(mode: Open_Flags) -> u32 { + cflags : u32 = 0 + + cflags |= OPEN_FLAG_RDONLY * u32(Open_Flags.RDONLY in mode) + cflags |= OPEN_FLAG_WRONLY * u32(Open_Flags.WRONLY in mode) + cflags |= OPEN_FLAG_RDWR * u32(Open_Flags.RDWR in mode) + cflags |= OPEN_FLAG_NONBLOCK * u32(Open_Flags.NONBLOCK in mode) + cflags |= OPEN_FLAG_CREAT * u32(Open_Flags.CREAT in mode) + cflags |= OPEN_FLAG_APPEND * u32(Open_Flags.APPEND in mode) + cflags |= OPEN_FLAG_TRUNC * u32(Open_Flags.TRUNC in mode) + cflags |= OPEN_FLAG_EXCL * u32(Open_Flags.EXCL in mode) + cflags |= OPEN_FLAG_SHLOCK * u32(Open_Flags.SHLOCK in mode) + cflags |= OPEN_FLAG_EXLOCK * u32(Open_Flags.EXLOCK in mode) + cflags |= OPEN_FLAG_DIRECTORY * u32(Open_Flags.DIRECTORY in mode) + cflags |= OPEN_FLAG_NOFOLLOW * u32(Open_Flags.NOFOLLOW in mode) + cflags |= OPEN_FLAG_SYMLINK * u32(Open_Flags.SYMLINK in mode) + cflags |= OPEN_FLAG_EVTONLY * u32(Open_Flags.EVTONLY in mode) + cflags |= OPEN_FLAG_CLOEXEC * u32(Open_Flags.CLOEXEC in mode) + cflags |= OPEN_FLAG_NOFOLLOW_ANY * u32(Open_Flags.NOFOLLOW_ANY in mode) + + return cflags +} + @(private) clone_to_cstring :: proc(s: string, allocator: runtime.Allocator, loc := #caller_location) -> cstring { c := make([]byte, len(s)+1, allocator, loc) @@ -109,22 +132,7 @@ sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, b cflags = _sys_permission_mode(mode) - cmode |= OPEN_FLAG_RDONLY * u32(Open_Flags.RDONLY in oflag) - cmode |= OPEN_FLAG_WRONLY * u32(Open_Flags.WRONLY in oflag) - cmode |= OPEN_FLAG_RDWR * u32(Open_Flags.RDWR in oflag) - cmode |= OPEN_FLAG_NONBLOCK * u32(Open_Flags.NONBLOCK in oflag) - cmode |= OPEN_FLAG_CREAT * u32(Open_Flags.CREAT in oflag) - cmode |= OPEN_FLAG_APPEND * u32(Open_Flags.APPEND in oflag) - cmode |= OPEN_FLAG_TRUNC * u32(Open_Flags.TRUNC in oflag) - cmode |= OPEN_FLAG_EXCL * u32(Open_Flags.EXCL in oflag) - cmode |= OPEN_FLAG_SHLOCK * u32(Open_Flags.SHLOCK in oflag) - cmode |= OPEN_FLAG_EXLOCK * u32(Open_Flags.EXLOCK in oflag) - cmode |= OPEN_FLAG_DIRECTORY * u32(Open_Flags.DIRECTORY in oflag) - cmode |= OPEN_FLAG_NOFOLLOW * u32(Open_Flags.NOFOLLOW in oflag) - cmode |= OPEN_FLAG_SYMLINK * u32(Open_Flags.SYMLINK in oflag) - cmode |= OPEN_FLAG_EVTONLY * u32(Open_Flags.EVTONLY in oflag) - cmode |= OPEN_FLAG_CLOEXEC * u32(Open_Flags.CLOEXEC in oflag) - cmode |= OPEN_FLAG_NOFOLLOW_ANY * u32(Open_Flags.NOFOLLOW_ANY in oflag) + cmode = _sys_open_mode(oflag) result := syscall_open(cpath, cmode, cflags) state := result != -1 @@ -187,3 +195,29 @@ sys_lstat :: proc(path: string, status: ^stat) -> bool { cpath: cstring = clone_to_cstring(path, context.temp_allocator) return syscall_lstat(cpath, status) != -1 } + +sys_shm_open :: proc(name: string, oflag: Open_Flags, mode: Permission) -> (c.int, bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cmode: u32 = 0 + cflags: u32 = 0 + cname: cstring = clone_to_cstring(name, context.temp_allocator) + + cflags = _sys_permission_mode(mode) + + cmode = _sys_open_mode(oflag) + + result := syscall_shm_open(cname, cmode, cflags) + state := result != -1 + + // NOTE(beau): Presently fstat doesn't report any changed permissions + // on the file descriptor even with this fchmod (which fails with a + // non-zero return). I can also reproduce this with the syscalls in c + // so I suspect it's not odin's bug. I've left the fchmod in case the + // underlying issue is fixed. + if state && cflags != 0 { + state = (syscall_fchmod(result, cflags) != -1) + } + + return result * cast(c.int)state, state +} diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index d289ee7c1..1188091a9 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -192,43 +192,43 @@ _STRUCT_TIMEVAL :: struct { /* pwd.h */ _Password_Entry :: struct { - pw_name: cstring, /* username */ - pw_passwd: cstring, /* user password */ - pw_uid: i32, /* user ID */ - pw_gid: i32, /* group ID */ + pw_name: cstring, /* username */ + pw_passwd: cstring, /* user password */ + pw_uid: i32, /* user ID */ + pw_gid: i32, /* group ID */ pw_change: u64, /* password change time */ pw_class: cstring, /* user access class */ - pw_gecos: cstring, /* full user name */ - pw_dir: cstring, /* home directory */ - pw_shell: cstring, /* shell program */ + pw_gecos: cstring, /* full user name */ + pw_dir: cstring, /* home directory */ + pw_shell: cstring, /* shell program */ pw_expire: u64, /* account expiration */ pw_fields: i32, /* filled fields */ } /* processinfo.h */ _Proc_Bsdinfo :: struct { - pbi_flags: u32, /* if is 64bit; emulated etc */ - pbi_status: u32, - pbi_xstatus: u32, - pbi_pid: u32, - pbi_ppid: u32, - pbi_uid: u32, - pbi_gid: u32, - pbi_ruid: u32, - pbi_rgid: u32, - pbi_svuid: u32, - pbi_svgid: u32, - res: u32, - pbi_comm: [DARWIN_MAXCOMLEN]u8, - pbi_name: [2 * DARWIN_MAXCOMLEN]u8, /* empty if no name is registered */ - pbi_nfiles: u32, - pbi_pgid: u32, - pbi_pjobc: u32, - e_tdev: u32, /* controlling tty dev */ - e_tpgid: u32, /* tty process group id */ - pbi_nice: i32, - pbi_start_tvsec: u64, - pbi_start_tvusec: u64, + pbi_flags: u32, /* if is 64bit; emulated etc */ + pbi_status: u32, + pbi_xstatus: u32, + pbi_pid: u32, + pbi_ppid: u32, + pbi_uid: u32, + pbi_gid: u32, + pbi_ruid: u32, + pbi_rgid: u32, + pbi_svuid: u32, + pbi_svgid: u32, + res: u32, + pbi_comm: [DARWIN_MAXCOMLEN]u8, + pbi_name: [2 * DARWIN_MAXCOMLEN]u8, /* empty if no name is registered */ + pbi_nfiles: u32, + pbi_pgid: u32, + pbi_pjobc: u32, + e_tdev: u32, /* controlling tty dev */ + e_tpgid: u32, /* tty process group id */ + pbi_nice: i32, + pbi_start_tvsec: u64, + pbi_start_tvusec: u64, } /*--==========================================================================--*/ @@ -425,3 +425,11 @@ syscall_fchdir :: #force_inline proc "contextless" (fd: c.int, path: cstring) -> syscall_getrusage :: #force_inline proc "contextless" (who: c.int, rusage: ^RUsage) -> c.int { return cast(c.int) intrinsics.syscall(unix_offset_syscall(.getrusage), uintptr(who), uintptr(rusage)) } + +syscall_shm_open :: #force_inline proc "contextless" (name: cstring, oflag: u32, mode: u32) -> c.int { + return cast(c.int)intrinsics.syscall(unix_offset_syscall(.shm_open), transmute(uintptr)name, uintptr(oflag), uintptr(mode)) +} + +syscall_shm_unlink :: #force_inline proc "contextless" (name: cstring) -> c.int { + return cast(c.int)intrinsics.syscall(unix_offset_syscall(.shm_unlink), transmute(uintptr)name) +} diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin new file mode 100644 index 000000000..8590df46e --- /dev/null +++ b/core/sys/freebsd/syscalls.odin @@ -0,0 +1,602 @@ +package sys_freebsd + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:c" + +// FreeBSD 15 syscall numbers +// See: https://alfonsosiciliano.gitlab.io/posts/2023-08-28-freebsd-15-system-calls.html + +SYS_read : uintptr : 3 +SYS_write : uintptr : 4 +SYS_open : uintptr : 5 +SYS_close : uintptr : 6 +SYS_getpid : uintptr : 20 +SYS_recvfrom : uintptr : 29 +SYS_accept : uintptr : 30 +SYS_fcntl : uintptr : 92 +SYS_fsync : uintptr : 95 +SYS_socket : uintptr : 97 +SYS_connect : uintptr : 98 +SYS_bind : uintptr : 104 +SYS_listen : uintptr : 106 +SYS_sendto : uintptr : 133 +SYS_shutdown : uintptr : 134 +SYS_setsockopt : uintptr : 105 +SYS_sysctl : uintptr : 202 +SYS__umtx_op : uintptr : 454 +SYS_pread : uintptr : 475 +SYS_pwrite : uintptr : 476 +SYS_accept4 : uintptr : 541 + +// +// Odin syscall wrappers +// + +// Read input. +// +// The read() function appeared in Version 1 AT&T UNIX. +read :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_read, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output. +// +// The write() function appeared in Version 1 AT&T UNIX. +write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Open or create a file for reading, writing or executing. +// +// The open() function appeared in Version 1 AT&T UNIX. +// The openat() function was introduced in FreeBSD 8.0. +open :: proc "contextless" (path: string, flags: File_Status_Flags, mode: int = 0o000) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_open, + cast(uintptr)raw_data(path), + cast(uintptr)transmute(c.int)flags, + cast(uintptr)mode) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +// Delete a descriptor. +// +// The open() function appeared in Version 1 AT&T UNIX. +close :: proc "contextless" (fd: Fd) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_close, + cast(uintptr)fd) + + return cast(Errno)result +} + +// Get parent or calling process identification. +// +// The getpid() function appeared in Version 7 AT&T UNIX. +getpid :: proc "contextless" () -> pid_t { + // This always succeeds. + result, _ := intrinsics.syscall_bsd(SYS_getpid) + return cast(pid_t)result +} + +// Receive message(s) from a socket. +// +// The recv() function appeared in 4.2BSD. +// The recvmmsg() function appeared in FreeBSD 11.0. +recvfrom :: proc "contextless" (s: Fd, buf: []u8, flags: Recv_Flags, from: ^$T) -> (int, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + fromlen: socklen_t = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_recvfrom, + cast(uintptr)s, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)flags, + cast(uintptr)from, + cast(uintptr)&fromlen) + + // `from.len` will be modified by the syscall, so we shouldn't need to pass + // `fromlen` back from this API. + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Receive message(s) from a socket. +// +// The recv() function appeared in 4.2BSD. +// The recvmmsg() function appeared in FreeBSD 11.0. +recv :: proc "contextless" (s: Fd, buf: []u8, flags: Recv_Flags) -> (int, Errno) { + // This is a wrapper over recvfrom(). + result, ok := intrinsics.syscall_bsd(SYS_recvfrom, + cast(uintptr)s, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)flags, + 0, + 0) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Accept a connection on a socket. +// +// The accept() system call appeared in 4.2BSD. +accept_T :: proc "contextless" (s: Fd, sockaddr: ^$T) -> (Fd, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + // sockaddr must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: socklen_t = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_accept, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen) + + if !ok { + return 0, cast(Errno)result + } + + sockaddr.len = cast(u8)addrlen + + return cast(Fd)result, nil +} + + +// Accept a connection on a socket. +// +// The accept() system call appeared in 4.2BSD. +accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_accept, + cast(uintptr)s, + cast(uintptr)0, + cast(uintptr)0) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +accept :: proc { accept_T, accept_nil } + +// Synchronize changes to a file. +// +// The fsync() system call appeared in 4.2BSD. +fsync :: proc "contextless" (fd: Fd) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fsync, + cast(uintptr)fd) + + return cast(Errno)result +} + +// File control. +// +// The fcntl() system call appeared in 4.2BSD. +// The F_DUP2FD constant first appeared in FreeBSD 7.1. +// +// NOTE: If you know at compile-time what command you're calling, use one of the +// `fcntl_*` procedures instead to preserve type safety. +fcntl :: proc "contextless" (fd: Fd, cmd: File_Control_Command, arg: c.int) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)cmd, + cast(uintptr)arg) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// TODO: Implement more type-safe fcntl commands. + +fcntl_dupfd :: proc "contextless" (fd: Fd, newfd: Fd) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.DUPFD, + cast(uintptr)newfd) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +fcntl_getfd :: proc "contextless" (fd: Fd) -> (bool, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETFD) + + if !ok { + return false, cast(Errno)result + } + + return result & FD_CLOEXEC > 0, nil +} + +fcntl_setfd :: proc "contextless" (fd: Fd, close_on_exec: bool) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETFD, + (close_on_exec ? FD_CLOEXEC : 0)) + + return cast(Errno)result +} + +fcntl_getfl :: proc "contextless" (fd: Fd) -> (File_Status_Flags, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETFL) + + if !ok { + return nil, cast(Errno)result + } + + return transmute(File_Status_Flags)cast(c.int)result, nil +} + +fcntl_setfl :: proc "contextless" (fd: Fd, flags: File_Status_Flags) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETFL, + cast(uintptr)transmute(c.int)flags) + + return cast(Errno)result +} + +fcntl_getown :: proc "contextless" (fd: Fd) -> (pid_t, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETOWN) + + if !ok { + return 0, cast(Errno)result + } + + return cast(pid_t)result, nil +} + +fcntl_setown :: proc "contextless" (fd: Fd, pid: pid_t) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETOWN, + cast(uintptr)pid) + + return cast(Errno)result +} + +fcntl_getlk :: proc "contextless" (fd: Fd, flock: ^File_Lock) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETLK, + cast(uintptr)flock) + + return cast(Errno)result +} + +fcntl_setlk :: proc "contextless" (fd: Fd, flock: ^File_Lock) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETLK, + cast(uintptr)flock) + + return cast(Errno)result +} + +fcntl_add_seals :: proc "contextless" (fd: Fd, seals: File_Seals) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.ADD_SEALS, + cast(uintptr)transmute(c.int)seals) + + return cast(Errno)result +} + +fcntl_get_seals :: proc "contextless" (fd: Fd) -> (File_Seals, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GET_SEALS) + + if !ok { + return nil, cast(Errno)result + } + + return transmute(File_Seals)cast(c.int)result, nil +} + +// +// End type-safe fcntl commands. +// + +// Create an endpoint for communication. +// +// The socket() system call appeared in 4.2BSD. +socket :: proc "contextless" (domain: Protocol_Family, type: Socket_Type, protocol: Protocol) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_socket, + cast(uintptr)domain, + cast(uintptr)type, + cast(uintptr)protocol) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +// Initiate a connection on a socket. +// +// The connect() system call appeared in 4.2BSD. +connect :: proc "contextless" (fd: Fd, sockaddr: ^$T, addrlen: socklen_t) -> Errno +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + result, _ := intrinsics.syscall_bsd(SYS_connect, + cast(uintptr)fd, + cast(uintptr)sockaddr, + cast(uintptr)addrlen) + + return cast(Errno)result +} + + +// Assign a local protocol address to a socket. +// +// The bind() system call appeared in 4.2BSD. +bind :: proc "contextless" (s: Fd, sockaddr: ^$T, addrlen: socklen_t) -> Errno +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + result, _ := intrinsics.syscall_bsd(SYS_bind, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)addrlen) + + return cast(Errno)result +} + +// Listen for connections on a socket. +// +// The listen() system call appeared in 4.2BSD. +listen :: proc "contextless" (s: Fd, backlog: int) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_listen, + cast(uintptr)s, + cast(uintptr)backlog) + + return cast(Errno)result +} + +// Send message(s) from a socket. +// +// The send() function appeared in 4.2BSD. +// The sendmmsg() function appeared in FreeBSD 11.0. +sendto :: proc "contextless" (s: Fd, msg: []u8, flags: Send_Flags, to: ^$T) -> (int, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + result, ok := intrinsics.syscall_bsd(SYS_sendto, + cast(uintptr)s, + cast(uintptr)raw_data(msg), + cast(uintptr)len(msg), + cast(uintptr)flags, + cast(uintptr)to, + cast(uintptr)to.len) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Send message(s) from a socket. +// +// The send() function appeared in 4.2BSD. +// The sendmmsg() function appeared in FreeBSD 11.0. +send :: proc "contextless" (s: Fd, msg: []u8, flags: Send_Flags) -> (int, Errno) { + // This is a wrapper over sendto(). + result, ok := intrinsics.syscall_bsd(SYS_sendto, + cast(uintptr)s, + cast(uintptr)raw_data(msg), + cast(uintptr)len(msg), + cast(uintptr)flags, + 0, + 0) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Disable sends and/or receives on a socket. +// +// The shutdown() system call appeared in 4.2BSD. +shutdown :: proc "contextless" (s: Fd, how: Shutdown_Method) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_shutdown, + cast(uintptr)s, + cast(uintptr)how) + + return cast(Errno)result +} + +// Get and set options on sockets. +// +// The getsockopt() and setsockopt() system calls appeared in 4.2BSD. +setsockopt :: proc "contextless" (s: Fd, level: Valid_Socket_Option_Level, optname: Socket_Option, optval: rawptr, optlen: socklen_t) -> Errno { + real_level: uintptr + switch which in level { + case Protocol_Family: real_level = cast(uintptr)which + case Socket_Option_Level: real_level = cast(uintptr)which + } + + result, _ := intrinsics.syscall_bsd(SYS_setsockopt, + cast(uintptr)s, + real_level, + cast(uintptr)optname, + cast(uintptr)optval, + cast(uintptr)optlen) + + return cast(Errno)result +} + +// Get or set system information. +// +// The sysctl() function first appeared in 4.4BSD. +sysctl :: proc "contextless" (mib: []MIB_Identifier, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_sysctl, + cast(uintptr)raw_data(mib), + cast(uintptr)len(mib), + cast(uintptr)oldp, + cast(uintptr)oldlenp, + cast(uintptr)newp, + cast(uintptr)newlen) + + return cast(Errno)result +} + +// Interface for implementation of userspace threading synchronization primitives. +// +// The _umtx_op() system call is non-standard and is used by the 1:1 Threading +// Library (libthr, -lthr) to implement IEEE Std 1003.1-2001 (“POSIX.1”) +// pthread(3) functionality. +_umtx_op :: proc "contextless" (obj: rawptr, op: Userland_Mutex_Operation, val: c.ulong, uaddr, uaddr2: rawptr) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS__umtx_op, + cast(uintptr)obj, + cast(uintptr)op, + cast(uintptr)val, + cast(uintptr)uaddr, + cast(uintptr)uaddr2) + + return cast(Errno)result +} + +// Read input without modifying the file pointer. +// +// The pread() function appeared in AT&T System V Release 4 UNIX. +pread :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pread, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output without modifying the file pointer. +// +// The pwrite() function appeared in AT&T System V Release 4 UNIX. +// +// BUGS +// +// The pwrite() system call appends the file without changing the file +// offset if O_APPEND is set, contrary to IEEE Std 1003.1-2008 (“POSIX.1”) +// where pwrite() writes into offset regardless of whether O_APPEND is set. +pwrite :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Accept a connection on a socket. +// +// The accept4() system call appeared in FreeBSD 10.0. +accept4_T :: proc "contextless" (s: Fd, sockaddr: ^$T, flags: Socket_Flags = {}) -> (Fd, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + // `sockaddr` must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: u32 = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_accept4, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen, + cast(uintptr)transmute(c.int)flags) + + if !ok { + return 0, cast(Errno)result + } + + sockaddr.len = cast(u8)addrlen + + return cast(Fd)result, nil +} + +// Accept a connection on a socket. +// +// The accept4() system call appeared in FreeBSD 10.0. +accept4_nil :: proc "contextless" (s: Fd, flags: Socket_Flags = {}) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_accept4, + cast(uintptr)s, + cast(uintptr)0, + cast(uintptr)0, + cast(uintptr)transmute(c.int)flags) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +accept4 :: proc { accept4_nil, accept4_T } diff --git a/core/sys/freebsd/types.odin b/core/sys/freebsd/types.odin new file mode 100644 index 000000000..37e8abf68 --- /dev/null +++ b/core/sys/freebsd/types.odin @@ -0,0 +1,1587 @@ +package sys_freebsd + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:c" + +// These definitions have been extracted from a system running FreeBSD 14.0-RELEASE. +// Most comments come from system header files. +// +// Where applicable, original C struct and define names are indicated in line +// comments above Odin declarations. +// +// This data is separated into blocks by original header file. If you happen to +// add or change something in this file, mind the organizational structure. + +Fd :: distinct c.int + +// +// #include +// & +// #include +// + +time_t :: distinct i64 + +// +// #include +// + +off_t :: distinct i64 +pid_t :: distinct i32 +sa_family_t :: distinct u8 +socklen_t :: distinct u32 +suseconds_t :: distinct c.long /* microseconds (signed) */ + +// +// #include +// + +in_port_t :: distinct u16be + +// +// #include +// + +timespec :: struct { + sec: time_t, /* seconds */ + nsec: c.long, /* and nanoseconds */ +} + +// +// #include +// + +timeval :: struct { + sec: time_t, /* seconds */ + usec: suseconds_t, /* and microseconds */ +} + +// +// #include +// + +Errno :: enum c.int { + 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, + ENOTSUP = EOPNOTSUPP, + 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, + 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, + EINTEGRITY = 97, +} + +// +// #include +// + +/* + * Types + */ +// #define SOCK_* +Socket_Type :: enum c.int { + STREAM = 1, /* stream socket */ + DGRAM = 2, /* datagram socket */ + RAW = 3, /* raw-protocol interface */ + RDM = 4, /* reliably-delivered message */ + SEQPACKET = 5, /* sequenced packet stream */ + + /* + * Creation flags, OR'ed into socket() and socketpair() type argument. + */ + CLOEXEC = 0x10000000, + NONBLOCK = 0x20000000, +} + +Socket_Flag_Index :: enum c.int { + CLOEXEC = 28, // 0x10000000 + NONBLOCK = 29, // 0x20000000 +} + +Socket_Flags :: bit_set[Socket_Flag_Index; c.int] + +/* + * Option flags per-socket. + */ +// #define SO_* +Socket_Option :: enum c.int { + DEBUG = 0x00000001, /* turn on debugging info recording */ + ACCEPTCONN = 0x00000002, /* socket has had listen() */ + REUSEADDR = 0x00000004, /* allow local address reuse */ + KEEPALIVE = 0x00000008, /* keep connections alive */ + DONTROUTE = 0x00000010, /* just use interface addresses */ + BROADCAST = 0x00000020, /* permit sending of broadcast msgs */ + USELOOPBACK = 0x00000040, /* bypass hardware when possible */ + LINGER = 0x00000080, /* linger on close if data present */ + OOBINLINE = 0x00000100, /* leave received OOB data in line */ + REUSEPORT = 0x00000200, /* allow local address & port reuse */ + TIMESTAMP = 0x00000400, /* timestamp received dgram traffic */ + NOSIGPIPE = 0x00000800, /* no SIGPIPE from EPIPE */ + ACCEPTFILTER = 0x00001000, /* there is an accept filter */ + BINTIME = 0x00002000, /* timestamp received dgram traffic */ + NO_OFFLOAD = 0x00004000, /* socket cannot be offloaded */ + NO_DDP = 0x00008000, /* disable direct data placement */ + REUSEPORT_LB = 0x00010000, /* reuse with load balancing */ + RERROR = 0x00020000, /* keep track of receive errors */ + + /* + * Additional options, not kept in so_options. + */ + SNDBUF = 0x1001, /* send buffer size */ + RCVBUF = 0x1002, /* receive buffer size */ + SNDLOWAT = 0x1003, /* send low-water mark */ + RCVLOWAT = 0x1004, /* receive low-water mark */ + SNDTIMEO = 0x1005, /* send timeout */ + RCVTIMEO = 0x1006, /* receive timeout */ + ERROR = 0x1007, /* get error status and clear */ + TYPE = 0x1008, /* get socket type */ + LABEL = 0x1009, /* socket's MAC label */ + PEERLABEL = 0x1010, /* socket's peer's MAC label */ + LISTENQLIMIT = 0x1011, /* socket's backlog limit */ + LISTENQLEN = 0x1012, /* socket's complete queue length */ + LISTENINCQLEN = 0x1013, /* socket's incomplete queue length */ + SETFIB = 0x1014, /* use this FIB to route */ + USER_COOKIE = 0x1015, /* user cookie (dummynet etc.) */ + PROTOCOL = 0x1016, /* get socket protocol (Linux name) */ + PROTOTYPE = PROTOCOL, /* alias for SO_PROTOCOL (SunOS name) */ + TS_CLOCK = 0x1017, /* clock type used for SO_TIMESTAMP */ + MAX_PACING_RATE = 0x1018, /* socket's max TX pacing rate (Linux name) */ + DOMAIN = 0x1019, /* get socket domain */ + + TS_REALTIME_MICRO = 0, /* microsecond resolution, realtime */ + TS_BINTIME = 1, /* sub-nanosecond resolution, realtime */ + TS_REALTIME = 2, /* nanosecond resolution, realtime */ + TS_MONOTONIC = 3, /* nanosecond resolution, monotonic */ + TS_DEFAULT = TS_REALTIME_MICRO, + TS_CLOCK_MAX = TS_MONOTONIC, +} + +Valid_Socket_Option_Level :: union #no_nil { + Protocol_Family, + Socket_Option_Level, +} + +/* + * Level number for (get/set)sockopt() to apply to socket itself. + */ +// #define SOL_* +Socket_Option_Level :: enum c.int { + SOCKET = 0xffff, /* options for socket level */ +} + +// #define MSG_* +Message_Flag :: enum c.int { + OOB = 0x00000001, /* process out-of-band data */ + PEEK = 0x00000002, /* peek at incoming message */ + DONTROUTE = 0x00000004, /* send without using routing tables */ + EOR = 0x00000008, /* data completes record */ + TRUNC = 0x00000010, /* data discarded before delivery */ + CTRUNC = 0x00000020, /* control data lost before delivery */ + WAITALL = 0x00000040, /* wait for full request or error */ + DONTWAIT = 0x00000080, /* this message should be nonblocking */ + EOF = 0x00000100, /* data completes connection */ + /* 0x00000200 unused */ + /* 0x00000400 unused */ + /* 0x00000800 unused */ + /* 0x00001000 unused */ + NOTIFICATION = 0x00002000, /* SCTP notification */ + NBIO = 0x00004000, /* FIONBIO mode, used by fifofs */ + COMPAT = 0x00008000, /* used in sendit() */ + SOCALLBCK = 0x00010000, /* for use by socket callbacks - soreceive (TCP) */ + NOSIGNAL = 0x00020000, /* do not generate SIGPIPE on EOF */ + CMSG_CLOEXEC = 0x00040000, /* make received fds close-on-exec */ + WAITFORONE = 0x00080000, /* for recvmmsg() */ +} + +// Specific subset of `MSG_*` defines that are only for `recv*`. +Recv_Flags :: enum c.int { + NONE = 0, + OOB = cast(c.int)Message_Flag.OOB, /* process out-of-band data */ + PEEK = cast(c.int)Message_Flag.PEEK, /* peek at incoming message */ + TRUNC = cast(c.int)Message_Flag.TRUNC, /* return real packet or datagram length */ + WAITALL = cast(c.int)Message_Flag.WAITALL, /* wait for full request or error */ + DONTWAIT = cast(c.int)Message_Flag.DONTWAIT, /* do not block */ + CMSG_CLOEXEC = cast(c.int)Message_Flag.CMSG_CLOEXEC, /* set received fds close-on-exec */ + WAITFORONE = cast(c.int)Message_Flag.WAITFORONE, /* do not block after receiving the first message */ +} + +// Specific subset of `MSG_*` defines that are only for `send*`. +Send_Flags :: enum c.int { + NONE = 0, + OOB = cast(c.int)Message_Flag.OOB, /* process out-of-band data */ + DONTROUTE = cast(c.int)Message_Flag.DONTROUTE, /* bypass routing, use direct interface */ + EOR = cast(c.int)Message_Flag.EOR, /* data completes record */ + DONTWAIT = cast(c.int)Message_Flag.DONTWAIT, /* do not block */ + EOF = cast(c.int)Message_Flag.EOF, /* data completes transaction */ + NOSIGNAL = cast(c.int)Message_Flag.NOSIGNAL, /* do not generate SIGPIPE on EOF */ +} + +// Socket address struct header without protocol-specific data. +// +// Inherit from this if you want a custom socket address datatype for use with +// bind(), listen(), et cetera. +Socket_Address_Header :: struct #packed { + len: c.uchar, /* address length */ + family: Address_Family, /* address family */ +} + +// struct sockaddr +Socket_Address_Basic :: struct #packed { + using _: Socket_Address_Header, + data: [14]c.char, +} + +/* + * howto arguments for shutdown(2), specified by Posix.1g. + */ +// #define SHUT_* +Shutdown_Method :: enum c.int { + RD = 0, /* shut down the reading side */ + WR = 1, /* shut down the writing side */ + RDWR = 2, /* shut down both sides */ +} + +// #define AF_* +Address_Family :: enum sa_family_t { + UNSPEC = 0, + LOCAL = 1, + UNIX = LOCAL, + INET = 2, + IMPLINK = 3, + PUP = 4, + CHAOS = 5, + NETBIOS = 6, + ISO = 7, + OSI = ISO, + ECMA = 8, + DATAKIT = 9, + CCITT = 10, + SNA = 11, + DECnet = 12, + DLI = 13, + LAT = 14, + HYLINK = 15, + APPLETALK = 16, + ROUTE = 17, + LINK = 18, + PSEUDO_XTP = 19, + COIP = 20, + CNT = 21, + PSEUDO_RTIP = 22, + IPX = 23, + SIP = 24, + PSEUDO_PIP = 25, + ISDN = 26, + E164 = ISDN, + PSEUDO_KEY = 27, + INET6 = 28, + NATM = 29, + ATM = 30, + NETGRAPH = 32, + SLOW = 33, + SCLUSTER = 34, + ARP = 35, + BLUETOOTH = 36, + IEEE80211 = 37, + NETLINK = 38, + INET_SDP = 40, + INET6_SDP = 42, + HYPERV = 43, + DIVERT = 44, + MAX = 44, + VENDOR00 = 39, + VENDOR01 = 41, + VENDOR03 = 45, + VENDOR04 = 47, + VENDOR05 = 49, + VENDOR06 = 51, + VENDOR07 = 53, + VENDOR08 = 55, + VENDOR09 = 57, + VENDOR10 = 59, + VENDOR11 = 61, + VENDOR12 = 63, + VENDOR13 = 65, + VENDOR14 = 67, + VENDOR15 = 69, + VENDOR16 = 71, + VENDOR17 = 73, + VENDOR18 = 75, + VENDOR19 = 77, + VENDOR20 = 79, + VENDOR21 = 81, + VENDOR22 = 83, + VENDOR23 = 85, + VENDOR24 = 87, + VENDOR25 = 89, + VENDOR26 = 91, + VENDOR27 = 93, + VENDOR28 = 95, + VENDOR29 = 97, + VENDOR30 = 99, + VENDOR31 = 101, + VENDOR32 = 103, + VENDOR33 = 105, + VENDOR34 = 107, + VENDOR35 = 109, + VENDOR36 = 111, + VENDOR37 = 113, + VENDOR38 = 115, + VENDOR39 = 117, + VENDOR40 = 119, + VENDOR41 = 121, + VENDOR42 = 123, + VENDOR43 = 125, + VENDOR44 = 127, + VENDOR45 = 129, + VENDOR46 = 131, + VENDOR47 = 133, +} + +// #define PF_* +Protocol_Family :: enum sa_family_t { + UNSPEC = cast(sa_family_t)Address_Family.UNSPEC, + LOCAL = cast(sa_family_t)Address_Family.LOCAL, + UNIX = LOCAL, + INET = cast(sa_family_t)Address_Family.INET, + IMPLINK = cast(sa_family_t)Address_Family.IMPLINK, + PUP = cast(sa_family_t)Address_Family.PUP, + CHAOS = cast(sa_family_t)Address_Family.CHAOS, + NETBIOS = cast(sa_family_t)Address_Family.NETBIOS, + ISO = cast(sa_family_t)Address_Family.ISO, + OSI = cast(sa_family_t)Address_Family.ISO, + ECMA = cast(sa_family_t)Address_Family.ECMA, + DATAKIT = cast(sa_family_t)Address_Family.DATAKIT, + CCITT = cast(sa_family_t)Address_Family.CCITT, + SNA = cast(sa_family_t)Address_Family.SNA, + DECnet = cast(sa_family_t)Address_Family.DECnet, + DLI = cast(sa_family_t)Address_Family.DLI, + LAT = cast(sa_family_t)Address_Family.LAT, + HYLINK = cast(sa_family_t)Address_Family.HYLINK, + APPLETALK = cast(sa_family_t)Address_Family.APPLETALK, + ROUTE = cast(sa_family_t)Address_Family.ROUTE, + LINK = cast(sa_family_t)Address_Family.LINK, + XTP = cast(sa_family_t)Address_Family.PSEUDO_XTP, + COIP = cast(sa_family_t)Address_Family.COIP, + CNT = cast(sa_family_t)Address_Family.CNT, + SIP = cast(sa_family_t)Address_Family.SIP, + IPX = cast(sa_family_t)Address_Family.IPX, + RTIP = cast(sa_family_t)Address_Family.PSEUDO_RTIP, + PIP = cast(sa_family_t)Address_Family.PSEUDO_PIP, + ISDN = cast(sa_family_t)Address_Family.ISDN, + KEY = cast(sa_family_t)Address_Family.PSEUDO_KEY, + INET6 = cast(sa_family_t)Address_Family.INET6, + NATM = cast(sa_family_t)Address_Family.NATM, + ATM = cast(sa_family_t)Address_Family.ATM, + NETGRAPH = cast(sa_family_t)Address_Family.NETGRAPH, + SLOW = cast(sa_family_t)Address_Family.SLOW, + SCLUSTER = cast(sa_family_t)Address_Family.SCLUSTER, + ARP = cast(sa_family_t)Address_Family.ARP, + BLUETOOTH = cast(sa_family_t)Address_Family.BLUETOOTH, + IEEE80211 = cast(sa_family_t)Address_Family.IEEE80211, + NETLINK = cast(sa_family_t)Address_Family.NETLINK, + INET_SDP = cast(sa_family_t)Address_Family.INET_SDP, + INET6_SDP = cast(sa_family_t)Address_Family.INET6_SDP, + DIVERT = cast(sa_family_t)Address_Family.DIVERT, + MAX = cast(sa_family_t)Address_Family.MAX, +} + +// +// /etc/protocols +// + +Protocol :: enum c.int { + IP = 0, + ICMP = 1, + IGMP = 2, + GGP = 3, + IP_ENCAP = 4, + ST2 = 5, + TCP = 6, + CBT = 7, + EGP = 8, + IGP = 9, + BBN_RCC_MON = 10, + NVP_II = 11, + PUP = 12, + ARGUS = 13, + EMCON = 14, + XNET = 15, + CHAOS = 16, + UDP = 17, + MUX = 18, + DCN_MEAS = 19, + HMP = 20, + PRM = 21, + XNS_IDP = 22, + TRUNK_1 = 23, + TRUNK_2 = 24, + LEAF_1 = 25, + LEAF_2 = 26, + RDP = 27, + IRTP = 28, + ISO_TP4 = 29, + NETBLT = 30, + MFE_NSP = 31, + MERIT_INP = 32, + DCCP = 33, + THREE_PC = 34, + IDPR = 35, + XTP = 36, + DDP = 37, + IDPR_CMTP = 38, + TP_PlusPlus = 39, + IL = 40, + IPV6 = 41, + SDRP = 42, + IPV6_ROUTE = 43, + IPV6_FRAG = 44, + IDRP = 45, + RSVP = 46, + GRE = 47, + DSR = 48, + BNA = 49, + ESP = 50, + AH = 51, + I_NLSP = 52, + SWIPE = 53, + NARP = 54, + MOBILE = 55, + TLSP = 56, + SKIP = 57, + IPV6_ICMP = 58, + IPV6_NONXT = 59, + IPV6_OPTS = 60, + CFTP = 62, + SAT_EXPAK = 64, + KRYPTOLAN = 65, + RVD = 66, + IPPC = 67, + SAT_MON = 69, + VISA = 70, + IPCV = 71, + CPNX = 72, + CPHB = 73, + WSN = 74, + PVP = 75, + BR_SAT_MON = 76, + SUN_ND = 77, + WB_MON = 78, + WB_EXPAK = 79, + ISO_IP = 80, + VMTP = 81, + SECURE_VMTP = 82, + VINES = 83, + TTP = 84, + IPTM = 84, + NSFNET_IGP = 85, + DGP = 86, + TCF = 87, + EIGRP = 88, + OSPFIGP = 89, + Sprite_RPC = 90, + LARP = 91, + MTP = 92, + AX_25 = 93, + IPIP = 94, + MICP = 95, + SCC_SP = 96, + ETHERIP = 97, + ENCAP = 98, + GMTP = 100, + IFMP = 101, + PNNI = 102, + PIM = 103, + ARIS = 104, + SCPS = 105, + QNX = 106, + A_N = 107, + IPComp = 108, + SNP = 109, + Compaq_Peer = 110, + IPX_in_IP = 111, + CARP = 112, + PGM = 113, + L2TP = 115, + DDX = 116, + IATP = 117, + STP = 118, + SRP = 119, + UTI = 120, + SMP = 121, + SM = 122, + PTP = 123, + ISIS = 124, + FIRE = 125, + CRTP = 126, + CRUDP = 127, + SSCOPMCE = 128, + IPLT = 129, + SPS = 130, + PIPE = 131, + SCTP = 132, + FC = 133, + RSVP_E2E_IGNORE = 134, + Mobility_Header = 135, + UDPLite = 136, + MPLS_IN_IP = 137, + MANET = 138, + HIP = 139, + SHIM6 = 140, + WESP = 141, + ROHC = 142, + PFSYNC = 240, + DIVERT = 258, +} + +// +// #include +// + +/* + * Constants used for fcntl(2) + */ + +/* command values */ +// #define F_* +File_Control_Command :: enum c.int { + DUPFD = 0, /* duplicate file descriptor */ + GETFD = 1, /* get file descriptor flags */ + SETFD = 2, /* set file descriptor flags */ + GETFL = 3, /* get file status flags */ + SETFL = 4, /* set file status flags */ + GETOWN = 5, /* get SIGIO/SIGURG proc/pgrp */ + SETOWN = 6, /* set SIGIO/SIGURG proc/pgrp */ + OGETLK = 7, /* get record locking information */ + OSETLK = 8, /* set record locking information */ + OSETLKW = 9, /* F_SETLK; wait if blocked */ + DUP2FD = 10, /* duplicate file descriptor to arg */ + GETLK = 11, /* get record locking information */ + SETLK = 12, /* set record locking information */ + SETLKW = 13, /* F_SETLK; wait if blocked */ + SETLK_REMOTE = 14, /* debugging support for remote locks */ + READAHEAD = 15, /* read ahead */ + RDAHEAD = 16, /* Darwin compatible read ahead */ + DUPFD_CLOEXEC = 17, /* Like F_DUPFD, but FD_CLOEXEC is set */ + DUP2FD_CLOEXEC = 18, /* Like F_DUP2FD, but FD_CLOEXEC is set */ + ADD_SEALS = 19, + GET_SEALS = 20, + ISUNIONSTACK = 21, /* Kludge for libc, don't use it. */ + KINFO = 22, /* Return kinfo_file for this fd */ +} + +/* Seals (F_ADD_SEALS, F_GET_SEALS). */ +// #define F_SEAL_* +File_Seal_Index :: enum c.int { + SEAL = 0, // 0x0001, /* Prevent adding sealings */ + SHRINK = 1, // 0x0002, /* May not shrink */ + GROW = 2, // 0x0004, /* May not grow */ + WRITE = 3, // 0x0008, /* May not write */ +} + +File_Seals :: bit_set[File_Seal_Index; c.int] + +/* file descriptor flags (F_GETFD, F_SETFD) */ +FD_CLOEXEC :: 1 /* close-on-exec flag */ + +/* record locking flags (F_GETLK, F_SETLK, F_SETLKW) */ +// #define F_* +Record_Lock_Flag :: enum c.int { + RDLCK = 1, /* shared or read lock */ + UNLCK = 2, /* unlock */ + WRLCK = 3, /* exclusive or write lock */ + UNLCKSYS = 4, /* purge locks for a given system ID */ + CANCEL = 5, /* cancel an async lock request */ +} + +// struct flock +File_Lock :: struct { + start: off_t, /* starting offset */ + len: off_t, /* len = 0 means until end of file */ + pid: pid_t, /* lock owner */ + type: Record_Lock_Flag, /* lock type: read/write, etc. */ + whence: c.short, /* type of l_start */ + sysid: c.int, /* remote system id or zero for local */ +} + +/* + * File status flags: these are used by open(2), fcntl(2). + * They are also used (indirectly) in the kernel file structure f_flags, + * which is a superset of the open/fcntl flags. Open flags and f_flags + * are inter-convertible using OFLAGS(fflags) and FFLAGS(oflags). + * Open/fcntl flags begin with O_; kernel-internal flags begin with F. + */ +File_Status_Flag :: enum c.int { + /* open-only flags */ + RDONLY = 0x0000, /* open for reading only */ + WRONLY = 0x0001, /* open for writing only */ + RDWR = 0x0002, /* open for reading and writing */ + ACCMODE = 0x0003, /* mask for above modes */ + + /**/ + NONBLOCK = 0x0004, /* no delay */ + APPEND = 0x0008, /* set append mode */ + SHLOCK = 0x0010, /* open with shared file lock */ + EXLOCK = 0x0020, /* open with exclusive file lock */ + ASYNC = 0x0040, /* signal pgrp when data ready */ + FSYNC = 0x0080, /* synchronous writes */ + SYNC = 0x0080, /* POSIX synonym for O_FSYNC */ + NOFOLLOW = 0x0100, /* don't follow symlinks */ + CREAT = 0x0200, /* create if nonexistent */ + TRUNC = 0x0400, /* truncate to zero length */ + EXCL = 0x0800, /* error if already exists */ + + /* Defined by POSIX 1003.1; BSD default, but must be distinct from O_RDONLY. */ + NOCTTY = 0x8000, /* don't assign controlling terminal */ + + /* Attempt to bypass buffer cache */ + DIRECT = 0x00010000, + + DIRECTORY = 0x00020000, /* Fail if not directory */ + EXEC = 0x00040000, /* Open for execute only */ + SEARCH = EXEC, + + /* Defined by POSIX 1003.1-2008; BSD default, but reserve for future use. */ + TTY_INIT = 0x00080000, /* Restore default termios attributes */ + + CLOEXEC = 0x00100000, + VERIFY = 0x00200000, /* open only after verification */ + PATH = 0x00400000, /* fd is only a path */ + RESOLVE_BENEATH = 0x00800000, /* Do not allow name resolution to walk out of cwd */ + DSYNC = 0x01000000, /* POSIX data sync */ + EMPTY_PATH = 0x02000000, +} + +File_Status_Index :: enum c.int { + // No RDONLY (0x00), as that is implied and also impossible to express in a bit_set. + + // The comments below come from the documentation for `fcntl`. + WRONLY = 0, + RDWR = 1, + + /* Non-blocking I/O; if no data is available to a read(2) + system call, or if a write(2) operation would block, the + read or write call returns -1 with the error EAGAIN. */ + NONBLOCK = 2, + + /* Force each write to append at the end of file; corresponds + to the O_APPEND flag of open(2). */ + APPEND = 3, + + SHLOCK = 4, + EXLOCK = 5, + + /* Enable the SIGIO signal to be sent to the process group when + I/O is possible, e.g., upon availability of data to be read. */ + ASYNC = 6, + + /* Enable synchronous writes. Corresponds to the O_SYNC flag + of open(2). O_FSYNC is an historical synonym for O_SYNC. */ + SYNC = 7, + + FSYNC = 7, + NOFOLLOW = 8, + CREAT = 9, + TRUNC = 10, + EXCL = 11, + + NOCTTY = 15, + + /* Minimize or eliminate the cache effects of reading and + writing. The system will attempt to avoid caching the data + you read or write. If it cannot avoid caching the data, it + will minimize the impact the data has on the cache. Use of + this flag can drastically reduce performance if not used + with care. */ + DIRECT = 16, + + DIRECTORY = 17, + EXEC = 18, + TTY_INIT = 19, + CLOEXEC = 20, + VERIFY = 21, + PATH = 22, + RESOLVE_BENEATH = 23, + + /* Enable synchronous data writes. Corresponds to the O_DSYNC + flag of open(2). */ + DSYNC = 24, + + EMPTY_PATH = 25, +} + +File_Status_Flags :: bit_set[File_Status_Index; c.int] + +// +// #include +// + +@private _SS_MAXSIZE :: 128 +@private _SS_ALIGNSIZE :: size_of(i64) +@private _SS_PAD1SIZE :: _SS_ALIGNSIZE - size_of(c.uchar) - size_of(Address_Family) +@private _SS_PAD2SIZE :: _SS_MAXSIZE - size_of(c.uchar) - size_of(Address_Family) - _SS_PAD1SIZE - _SS_ALIGNSIZE + +/* + * RFC 2553: protocol-independent placeholder for socket addresses + */ +// struct sockaddr_storage +Socket_Address_Storage :: struct { + using _: Socket_Address_Header, + _pad1: [_SS_PAD1SIZE]c.char, + _align: i64, /* force desired struct alignment */ + _pad2: [_SS_PAD2SIZE]c.char, +} + +// +// #include +// + +// MIB, or Management Information Base. Used in sysctl(). +MIB_Identifier :: enum c.int { + /* + * Top-level identifiers + */ + CTL_SYSCTL = 0, /* "magic" numbers */ + CTL_KERN = 1, /* "high kernel": proc, limits */ + CTL_VM = 2, /* virtual memory */ + CTL_VFS = 3, /* filesystem, mount type is next */ + CTL_NET = 4, /* network, see socket.h */ + CTL_DEBUG = 5, /* debugging parameters */ + CTL_HW = 6, /* generic cpu/io */ + CTL_MACHDEP = 7, /* machine dependent */ + CTL_USER = 8, /* user-level */ + CTL_P1003_1B = 9, /* POSIX 1003.1B */ + + /* + * CTL_SYSCTL identifiers + */ + CTL_SYSCTL_DEBUG = 0, /* printf all nodes */ + CTL_SYSCTL_NAME = 1, /* string name of OID */ + CTL_SYSCTL_NEXT = 2, /* next OID, honoring CTLFLAG_SKIP */ + CTL_SYSCTL_NAME2OID = 3, /* int array of name */ + CTL_SYSCTL_OIDFMT = 4, /* OID's kind and format */ + CTL_SYSCTL_OIDDESCR = 5, /* OID's description */ + CTL_SYSCTL_OIDLABEL = 6, /* aggregation label */ + CTL_SYSCTL_NEXTNOSKIP = 7, /* next OID, ignoring CTLFLAG_SKIP */ + + /* + * CTL_KERN identifiers + */ + KERN_OSTYPE = 1, /* string: system version */ + KERN_OSRELEASE = 2, /* string: system release */ + KERN_OSREV = 3, /* int: system revision */ + KERN_VERSION = 4, /* string: compile time info */ + KERN_MAXVNODES = 5, /* int: max vnodes */ + KERN_MAXPROC = 6, /* int: max processes */ + KERN_MAXFILES = 7, /* int: max open files */ + KERN_ARGMAX = 8, /* int: max arguments to exec */ + KERN_SECURELVL = 9, /* int: system security level */ + KERN_HOSTNAME = 10, /* string: hostname */ + KERN_HOSTID = 11, /* int: host identifier */ + KERN_CLOCKRATE = 12, /* struct: struct clockrate */ + /* was: #define KERN_VNODE13; disabled in 2003 and removed in 2023 */ + KERN_PROC = 14, /* struct: process entries */ + KERN_FILE = 15, /* struct: file entries */ + KERN_PROF = 16, /* node: kernel profiling info */ + KERN_POSIX1 = 17, /* int: POSIX.1 version */ + KERN_NGROUPS = 18, /* int: # of supplemental group ids */ + KERN_JOB_CONTROL = 19, /* int: is job control available */ + KERN_SAVED_IDS = 20, /* int: saved set-user/group-ID */ + KERN_BOOTTIME = 21, /* struct: time kernel was booted */ + KERN_NISDOMAINNAME = 22, /* string: YP domain name */ + KERN_UPDATEINTERVAL = 23, /* int: update process sleep time */ + KERN_OSRELDATE = 24, /* int: kernel release date */ + KERN_NTP_PLL = 25, /* node: NTP PLL control */ + KERN_BOOTFILE = 26, /* string: name of booted kernel */ + KERN_MAXFILESPERPROC = 27, /* int: max open files per proc */ + KERN_MAXPROCPERUID = 28, /* int: max processes per uid */ + KERN_DUMPDEV = 29, /* struct cdev *: device to dump on */ + KERN_IPC = 30, /* node: anything related to IPC */ + KERN_DUMMY = 31, /* unused */ + KERN_PS_STRINGS = 32, /* int: address of PS_STRINGS */ + KERN_USRSTACK = 33, /* int: address of USRSTACK */ + KERN_LOGSIGEXIT = 34, /* int: do we log sigexit procs? */ + KERN_IOV_MAX = 35, /* int: value of UIO_MAXIOV */ + KERN_HOSTUUID = 36, /* string: host UUID identifier */ + KERN_ARND = 37, /* int: from arc4rand() */ + KERN_MAXPHYS = 38, /* int: MAXPHYS value */ + KERN_LOCKF = 39, /* struct: lockf reports */ + /* + * KERN_PROC subtypes + */ + KERN_PROC_ALL = 0, /* everything */ + KERN_PROC_PID = 1, /* by process id */ + KERN_PROC_PGRP = 2, /* by process group id */ + KERN_PROC_SESSION = 3, /* by session of pid */ + KERN_PROC_TTY = 4, /* by controlling tty */ + KERN_PROC_UID = 5, /* by effective uid */ + KERN_PROC_RUID = 6, /* by real uid */ + KERN_PROC_ARGS = 7, /* get/set arguments/proctitle */ + KERN_PROC_PROC = 8, /* only return procs */ + KERN_PROC_SV_NAME = 9, /* get syscall vector name */ + KERN_PROC_RGID = 10, /* by real group id */ + KERN_PROC_GID = 11, /* by effective group id */ + KERN_PROC_PATHNAME = 12, /* path to executable */ + KERN_PROC_OVMMAP = 13, /* Old VM map entries for process */ + KERN_PROC_OFILEDESC = 14, /* Old file descriptors for process */ + KERN_PROC_KSTACK = 15, /* Kernel stacks for process */ + KERN_PROC_INC_THREAD = 0x10, /* modifier for pid, pgrp, tty, uid, ruid, gid, rgid and proc. This effectively uses 16-31 */ + KERN_PROC_VMMAP = 32, /* VM map entries for process */ + KERN_PROC_FILEDESC = 33, /* File descriptors for process */ + KERN_PROC_GROUPS = 34, /* process groups */ + KERN_PROC_ENV = 35, /* get environment */ + KERN_PROC_AUXV = 36, /* get ELF auxiliary vector */ + KERN_PROC_RLIMIT = 37, /* process resource limits */ + KERN_PROC_PS_STRINGS = 38, /* get ps_strings location */ + KERN_PROC_UMASK = 39, /* process umask */ + KERN_PROC_OSREL = 40, /* osreldate for process binary */ + KERN_PROC_SIGTRAMP = 41, /* signal trampoline location */ + KERN_PROC_CWD = 42, /* process current working directory */ + KERN_PROC_NFDS = 43, /* number of open file descriptors */ + KERN_PROC_SIGFASTBLK = 44, /* address of fastsigblk magic word */ + KERN_PROC_VM_LAYOUT = 45, /* virtual address space layout info */ + + /* + * KERN_IPC identifiers + */ + KIPC_MAXSOCKBUF = 1, /* int: max size of a socket buffer */ + KIPC_SOCKBUF_WASTE = 2, /* int: wastage factor in sockbuf */ + KIPC_SOMAXCONN = 3, /* int: max length of connection q */ + KIPC_MAX_LINKHDR = 4, /* int: max length of link header */ + KIPC_MAX_PROTOHDR = 5, /* int: max length of network header */ + KIPC_MAX_HDR = 6, /* int: max total length of headers */ + KIPC_MAX_DATALEN = 7, /* int: max length of data? */ + + /* + * Definitions for network related sysctl, CTL_NET. + * + * Second level is protocol family. + * Third level is protocol number. + * + * Further levels are defined by the individual families. + */ + + /* + * PF_ROUTE - Routing table + * + * Three additional levels are defined: + * Fourth: address family, 0 is wildcard + * Fifth: type of info, defined below + * Sixth: flag(s) to mask with for NET_RT_FLAGS + */ + NET_RT_DUMP = 1, /* dump; may limit to a.f. */ + NET_RT_FLAGS = 2, /* by flags, e.g. RESOLVING */ + NET_RT_IFLIST = 3, /* survey interface list */ + NET_RT_IFMALIST = 4, /* return multicast address list */ + NET_RT_IFLISTL = 5, /* Survey interface list, using 'l'en versions of msghdr structs. */ + NET_RT_NHOP = 6, /* dump routing nexthops */ + NET_RT_NHGRP = 7, /* dump routing nexthop groups */ + + /* + * CTL_HW identifiers + */ + HW_MACHINE = 1, /* string: machine class */ + HW_MODEL = 2, /* string: specific machine model */ + HW_NCPU = 3, /* int: number of cpus */ + HW_BYTEORDER = 4, /* int: machine byte order */ + HW_PHYSMEM = 5, /* int: total memory */ + HW_USERMEM = 6, /* int: non-kernel memory */ + HW_PAGESIZE = 7, /* int: software page size */ + HW_DISKNAMES = 8, /* strings: disk drive names */ + HW_DISKSTATS = 9, /* struct: diskstats[] */ + HW_FLOATINGPT = 10, /* int: has HW floating point? */ + HW_MACHINE_ARCH = 11, /* string: machine architecture */ + HW_REALMEM = 12, /* int: 'real' memory */ + + /* + * CTL_USER definitions + */ + USER_CS_PATH = 1, /* string: _CS_PATH */ + USER_BC_BASE_MAX = 2, /* int: BC_BASE_MAX */ + USER_BC_DIM_MAX = 3, /* int: BC_DIM_MAX */ + USER_BC_SCALE_MAX = 4, /* int: BC_SCALE_MAX */ + USER_BC_STRING_MAX = 5, /* int: BC_STRING_MAX */ + USER_COLL_WEIGHTS_MAX = 6, /* int: COLL_WEIGHTS_MAX */ + USER_EXPR_NEST_MAX = 7, /* int: EXPR_NEST_MAX */ + USER_LINE_MAX = 8, /* int: LINE_MAX */ + USER_RE_DUP_MAX = 9, /* int: RE_DUP_MAX */ + USER_POSIX2_VERSION = 10, /* int: POSIX2_VERSION */ + USER_POSIX2_C_BIND = 11, /* int: POSIX2_C_BIND */ + USER_POSIX2_C_DEV = 12, /* int: POSIX2_C_DEV */ + USER_POSIX2_CHAR_TERM = 13, /* int: POSIX2_CHAR_TERM */ + USER_POSIX2_FORT_DEV = 14, /* int: POSIX2_FORT_DEV */ + USER_POSIX2_FORT_RUN = 15, /* int: POSIX2_FORT_RUN */ + USER_POSIX2_LOCALEDEF = 16, /* int: POSIX2_LOCALEDEF */ + USER_POSIX2_SW_DEV = 17, /* int: POSIX2_SW_DEV */ + USER_POSIX2_UPE = 18, /* int: POSIX2_UPE */ + USER_STREAM_MAX = 19, /* int: POSIX2_STREAM_MAX */ + USER_TZNAME_MAX = 20, /* int: POSIX2_TZNAME_MAX */ + USER_LOCALBASE = 21, /* string: _PATH_LOCALBASE */ + + CTL_P1003_1B_ASYNCHRONOUS_IO = 1, /* boolean */ + CTL_P1003_1B_MAPPED_FILES = 2, /* boolean */ + CTL_P1003_1B_MEMLOCK = 3, /* boolean */ + CTL_P1003_1B_MEMLOCK_RANGE = 4, /* boolean */ + CTL_P1003_1B_MEMORY_PROTECTION = 5, /* boolean */ + CTL_P1003_1B_MESSAGE_PASSING = 6, /* boolean */ + CTL_P1003_1B_PRIORITIZED_IO = 7, /* boolean */ + CTL_P1003_1B_PRIORITY_SCHEDULING = 8, /* boolean */ + CTL_P1003_1B_REALTIME_SIGNALS = 9, /* boolean */ + CTL_P1003_1B_SEMAPHORES = 10, /* boolean */ + CTL_P1003_1B_FSYNC = 11, /* boolean */ + CTL_P1003_1B_SHARED_MEMORY_OBJECTS = 12, /* boolean */ + CTL_P1003_1B_SYNCHRONIZED_IO = 13, /* boolean */ + CTL_P1003_1B_TIMERS = 14, /* boolean */ + CTL_P1003_1B_AIO_LISTIO_MAX = 15, /* int */ + CTL_P1003_1B_AIO_MAX = 16, /* int */ + CTL_P1003_1B_AIO_PRIO_DELTA_MAX = 17, /* int */ + CTL_P1003_1B_DELAYTIMER_MAX = 18, /* int */ + CTL_P1003_1B_MQ_OPEN_MAX = 19, /* int */ + CTL_P1003_1B_PAGESIZE = 20, /* int */ + CTL_P1003_1B_RTSIG_MAX = 21, /* int */ + CTL_P1003_1B_SEM_NSEMS_MAX = 22, /* int */ + CTL_P1003_1B_SEM_VALUE_MAX = 23, /* int */ + CTL_P1003_1B_SIGQUEUE_MAX = 24, /* int */ + CTL_P1003_1B_TIMER_MAX = 25, /* int */ +} + +// +// #include +// + +// struct rt_metrics +Route_Metrics :: struct { + locks: c.ulong, /* Kernel must leave these values alone */ + mtu: c.ulong, /* MTU for this path */ + hopcount: c.ulong, /* max hops expected */ + expire: c.ulong, /* lifetime for route, e.g. redirect */ + recvpipe: c.ulong, /* inbound delay-bandwidth product */ + sendpipe: c.ulong, /* outbound delay-bandwidth product */ + ssthresh: c.ulong, /* outbound gateway buffer limit */ + rtt: c.ulong, /* estimated round trip time */ + rttvar: c.ulong, /* estimated rtt variance */ + pksent: c.ulong, /* packets sent using this route */ + weight: c.ulong, /* route weight */ + nhidx: c.ulong, /* route nexhop index */ + filler: [2]c.ulong, /* will be used for T/TCP later */ +} + +// struct rt_msghdr +Route_Message_Header :: struct { + msglen: c.ushort, /* to skip over non-understood messages */ + version: c.uchar, /* future binary compatibility */ + type: Route_Message_Type, /* message type */ + index: c.ushort, /* index for associated ifp */ + _spare1: c.ushort, + flags: c.int, /* flags, incl. kern & message, e.g. DONE */ + addrs: c.int, /* bitmask identifying sockaddrs in msg */ + pid: pid_t, /* identify sender */ + seq: c.int, /* for sender to identify action */ + errno: c.int, /* why failed */ + fmask: c.int, /* bitmask used in RTM_CHANGE message */ + inits: c.ulong, /* which metrics we are initializing */ + rmx: Route_Metrics, /* metrics themselves */ +} + +RTM_VERSION :: 5 /* Up the ante and ignore older versions */ + +/* + * Message types. + * + * The format for each message is annotated below using the following + * identifiers: + * + * (1) struct rt_msghdr + * (2) struct ifa_msghdr + * (3) struct if_msghdr + * (4) struct ifma_msghdr + * (5) struct if_announcemsghdr + * + */ +// #define RTM_* +Route_Message_Type :: enum c.uchar { + ADD = 0x1, /* (1) Add Route */ + DELETE = 0x2, /* (1) Delete Route */ + CHANGE = 0x3, /* (1) Change Metrics or flags */ + GET = 0x4, /* (1) Report Metrics */ + LOSING = 0x5, /* (1) Kernel Suspects Partitioning */ + REDIRECT = 0x6, /* (1) Told to use different route */ + MISS = 0x7, /* (1) Lookup failed on this address */ + LOCK = 0x8, /* (1) fix specified metrics */ + /* = 0x9 */ + /* = 0xa */ + RESOLVE = 0xb, /* (1) req to resolve dst to LL addr */ + NEWADDR = 0xc, /* (2) address being added to iface */ + DELADDR = 0xd, /* (2) address being removed from iface */ + IFINFO = 0xe, /* (3) iface going up/down etc. */ + NEWMADDR = 0xf, /* (4) mcast group membership being added to if */ + DELMADDR = 0x10, /* (4) mcast group membership being deleted */ + IFANNOUNCE = 0x11, /* (5) iface arrival/departure */ + IEEE80211 = 0x12, /* (5) IEEE80211 wireless event */ +} + +/* + * Bitmask values for rtm_addrs. + */ +// #define RTA_* +Route_Address_Flag :: enum c.int { + DST = 0x1, /* destination sockaddr present */ + GATEWAY = 0x2, /* gateway sockaddr present */ + NETMASK = 0x4, /* netmask sockaddr present */ + GENMASK = 0x8, /* cloning mask sockaddr present */ + IFP = 0x10, /* interface name sockaddr present */ + IFA = 0x20, /* interface addr sockaddr present */ + AUTHOR = 0x40, /* sockaddr for author of redirect */ + BRD = 0x80, /* for NEWADDR, broadcast or p-p dest addr */ +} + +/* + * Index offsets for sockaddr array for alternate internal encoding. + */ +// #define RTAX_* +Route_Address_Index :: enum c.int { + DST = 0, /* destination sockaddr present */ + GATEWAY = 1, /* gateway sockaddr present */ + NETMASK = 2, /* netmask sockaddr present */ + GENMASK = 3, /* cloning mask sockaddr present */ + IFP = 4, /* interface name sockaddr present */ + IFA = 5, /* interface addr sockaddr present */ + AUTHOR = 6, /* sockaddr for author of redirect */ + BRD = 7, /* for NEWADDR, broadcast or p-p dest addr */ + MAX = 8, /* size of array to allocate */ +} + +// The value stored in rtm_addrs and similar (ifm_addrs, etc.) +Route_Address_Flags :: bit_set[Route_Address_Index; c.int] + +// +// #include +// + +/* + * Values for if_link_state. + */ +// #define LINK_STATE_* +Link_State :: enum u8 { + UNKNOWN = 0, /* link invalid/unknown */ + DOWN = 1, /* link is down */ + UP = 2, /* link is up */ +} + +/* + * Structure describing information about an interface + * which may be of interest to management entities. + */ +// struct if_data +Interface_Data :: struct { + /* generic interface information */ + type: u8, /* ethernet, tokenring, etc */ + physical: u8, /* e.g., AUI, Thinnet, 10base-T, etc */ + addrlen: u8, /* media address length */ + hdrlen: u8, /* media header length */ + link_state: Link_State, /* current link state */ + vhid: u8, /* carp vhid */ + datalen: u16, /* length of this data struct */ + mtu: u32, /* maximum transmission unit */ + metric: u32, /* routing metric (external only) */ + baudrate: u64, /* linespeed */ + /* volatile statistics */ + ipackets: u64, /* packets received on interface */ + ierrors: u64, /* input errors on interface */ + opackets: u64, /* packets sent on interface */ + oerrors: u64, /* output errors on interface */ + collisions: u64, /* collisions on csma interfaces */ + ibytes: u64, /* total number of octets received */ + obytes: u64, /* total number of octets sent */ + imcasts: u64, /* packets received via multicast */ + omcasts: u64, /* packets sent via multicast */ + iqdrops: u64, /* dropped on input */ + oqdrops: u64, /* dropped on output */ + noproto: u64, /* destined for unsupported protocol */ + hwassist: u64, /* HW offload capabilities, see IFCAP */ + + /* Unions are here to make sizes MI. */ + _epoch: struct #raw_union { /* uptime at attach or stat reset */ + tt: time_t, + ph: u64, + }, + + _lastchange: struct #raw_union { /* time of last administrative change */ + tv: timeval, + ph: struct { + ph1: u64, + ph2: u64, + }, + }, +} + +/* + * The 'l' version shall be used by new interfaces, like NET_RT_IFLISTL. It is + * extensible after ifm_data_off or within ifm_data. Both the if_msghdr and + * if_data now have a member field detailing the struct length in addition to + * the routing message length. Macros are provided to find the start of + * ifm_data and the start of the socket address strucutres immediately following + * struct if_msghdrl given a pointer to struct if_msghdrl. + */ +// struct if_msghdrl +Interface_Message_Header_Len :: struct { + msglen: c.ushort, /* to skip over non-understood messages */ + version: c.uchar, /* future binary compatibility */ + type: c.uchar, /* message type */ + addrs: Route_Address_Flags, /* like rtm_addrs */ + flags: c.int, /* value of if_flags */ + index: c.ushort, /* index for associated ifp */ + _spare1: c.ushort, /* spare space to grow if_index, see if_var.h */ + len: c.ushort, /* length of if_msghdrl incl. if_data */ + data_off: c.ushort, /* offset of if_data from beginning */ + _spare2: c.int, + data: Interface_Data, /* statistics and other data about if */ +} + +/* + * The 'l' version shall be used by new interfaces, like NET_RT_IFLISTL. It is + * extensible after ifam_metric or within ifam_data. Both the ifa_msghdrl and + * if_data now have a member field detailing the struct length in addition to + * the routing message length. Macros are provided to find the start of + * ifm_data and the start of the socket address strucutres immediately following + * struct ifa_msghdrl given a pointer to struct ifa_msghdrl. + */ +// struct ifa_msghdrl +Interface_Address_Message_Header_Len :: struct { + msglen: c.ushort, /* to skip over non-understood messages */ + version: c.uchar, /* future binary compatibility */ + type: c.uchar, /* message type */ + addrs: Route_Address_Flags, /* like rtm_addrs */ + flags: c.int, /* value of ifa_flags */ + index: c.ushort, /* index for associated ifp */ + _spare1: c.ushort, /* spare space to grow if_index, see if_var.h */ + len: c.ushort, /* length of ifa_msghdrl incl. if_data */ + data_off: c.ushort, /* offset of if_data from beginning */ + metric: c.int, /* value of ifa_ifp->if_metric */ + data: Interface_Data, /* statistics and other data about if or address */ +} + +// +// #include +// + +// enum ifType +Interface_Type :: enum c.uchar { + OTHER = 0x1, /* none of the following */ + ARPA_1822 = 0x2, /* old-style arpanet imp */ + HDH1822 = 0x3, /* HDH arpanet imp */ + X25DDN = 0x4, /* x25 to imp */ + X25 = 0x5, /* PDN X25 interface (RFC877) */ + ETHER = 0x6, /* Ethernet CSMA/CD */ + ISO88023 = 0x7, /* CMSA/CD */ + ISO88024 = 0x8, /* Token Bus */ + ISO88025 = 0x9, /* Token Ring */ + ISO88026 = 0xa, /* MAN */ + STARLAN = 0xb, + P10 = 0xc, /* Proteon 10MBit ring */ + P80 = 0xd, /* Proteon 80MBit ring */ + HY = 0xe, /* Hyperchannel */ + FDDI = 0xf, + LAPB = 0x10, + SDLC = 0x11, + T1 = 0x12, + CEPT = 0x13, /* E1 - european T1 */ + ISDNBASIC = 0x14, + ISDNPRIMARY = 0x15, + PTPSERIAL = 0x16, /* Proprietary PTP serial */ + PPP = 0x17, /* RFC 1331 */ + LOOP = 0x18, /* loopback */ + EON = 0x19, /* ISO over IP */ + XETHER = 0x1a, /* obsolete 3MB experimental ethernet */ + NSIP = 0x1b, /* XNS over IP */ + SLIP = 0x1c, /* IP over generic TTY */ + ULTRA = 0x1d, /* Ultra Technologies */ + DS3 = 0x1e, /* Generic T3 */ + SIP = 0x1f, /* SMDS */ + FRELAY = 0x20, /* Frame Relay DTE only */ + RS232 = 0x21, + PARA = 0x22, /* parallel-port */ + ARCNET = 0x23, + ARCNETPLUS = 0x24, + ATM = 0x25, /* ATM cells */ + MIOX25 = 0x26, + SONET = 0x27, /* SONET or SDH */ + X25PLE = 0x28, + ISO88022LLC = 0x29, + LOCALTALK = 0x2a, + SMDSDXI = 0x2b, + FRELAYDCE = 0x2c, /* Frame Relay DCE */ + V35 = 0x2d, + HSSI = 0x2e, + HIPPI = 0x2f, + MODEM = 0x30, /* Generic Modem */ + AAL5 = 0x31, /* AAL5 over ATM */ + SONETPATH = 0x32, + SONETVT = 0x33, + SMDSICIP = 0x34, /* SMDS InterCarrier Interface */ + PROPVIRTUAL = 0x35, /* Proprietary Virtual/internal */ + PROPMUX = 0x36, /* Proprietary Multiplexing */ + IEEE80212 = 0x37, /* 100BaseVG */ + FIBRECHANNEL = 0x38, /* Fibre Channel */ + HIPPIINTERFACE = 0x39, /* HIPPI interfaces */ + FRAMERELAYINTERCONNECT = 0x3a, /* Obsolete, use 0x20 either 0x2c */ + AFLANE8023 = 0x3b, /* ATM Emulated LAN for 802.3 */ + AFLANE8025 = 0x3c, /* ATM Emulated LAN for 802.5 */ + CCTEMUL = 0x3d, /* ATM Emulated circuit */ + FASTETHER = 0x3e, /* Fast Ethernet (100BaseT) */ + ISDN = 0x3f, /* ISDN and X.25 */ + V11 = 0x40, /* CCITT V.11/X.21 */ + V36 = 0x41, /* CCITT V.36 */ + G703AT64K = 0x42, /* CCITT G703 at 64Kbps */ + G703AT2MB = 0x43, /* Obsolete see DS1-MIB */ + QLLC = 0x44, /* SNA QLLC */ + FASTETHERFX = 0x45, /* Fast Ethernet (100BaseFX) */ + CHANNEL = 0x46, /* channel */ + IEEE80211 = 0x47, /* radio spread spectrum (unused) */ + IBM370PARCHAN = 0x48, /* IBM System 360/370 OEMI Channel */ + ESCON = 0x49, /* IBM Enterprise Systems Connection */ + DLSW = 0x4a, /* Data Link Switching */ + ISDNS = 0x4b, /* ISDN S/T interface */ + ISDNU = 0x4c, /* ISDN U interface */ + LAPD = 0x4d, /* Link Access Protocol D */ + IPSWITCH = 0x4e, /* IP Switching Objects */ + RSRB = 0x4f, /* Remote Source Route Bridging */ + ATMLOGICAL = 0x50, /* ATM Logical Port */ + DS0 = 0x51, /* Digital Signal Level 0 */ + DS0BUNDLE = 0x52, /* group of ds0s on the same ds1 */ + BSC = 0x53, /* Bisynchronous Protocol */ + ASYNC = 0x54, /* Asynchronous Protocol */ + CNR = 0x55, /* Combat Net Radio */ + ISO88025DTR = 0x56, /* ISO 802.5r DTR */ + EPLRS = 0x57, /* Ext Pos Loc Report Sys */ + ARAP = 0x58, /* Appletalk Remote Access Protocol */ + PROPCNLS = 0x59, /* Proprietary Connectionless Protocol*/ + HOSTPAD = 0x5a, /* CCITT-ITU X.29 PAD Protocol */ + TERMPAD = 0x5b, /* CCITT-ITU X.3 PAD Facility */ + FRAMERELAYMPI = 0x5c, /* Multiproto Interconnect over FR */ + X213 = 0x5d, /* CCITT-ITU X213 */ + ADSL = 0x5e, /* Asymmetric Digital Subscriber Loop */ + RADSL = 0x5f, /* Rate-Adapt. Digital Subscriber Loop*/ + SDSL = 0x60, /* Symmetric Digital Subscriber Loop */ + VDSL = 0x61, /* Very H-Speed Digital Subscrib. Loop*/ + ISO88025CRFPINT = 0x62, /* ISO 802.5 CRFP */ + MYRINET = 0x63, /* Myricom Myrinet */ + VOICEEM = 0x64, /* voice recEive and transMit */ + VOICEFXO = 0x65, /* voice Foreign Exchange Office */ + VOICEFXS = 0x66, /* voice Foreign Exchange Station */ + VOICEENCAP = 0x67, /* voice encapsulation */ + VOICEOVERIP = 0x68, /* voice over IP encapsulation */ + ATMDXI = 0x69, /* ATM DXI */ + ATMFUNI = 0x6a, /* ATM FUNI */ + ATMIMA = 0x6b, /* ATM IMA */ + PPPMULTILINKBUNDLE = 0x6c, /* PPP Multilink Bundle */ + IPOVERCDLC = 0x6d, /* IBM ipOverCdlc */ + IPOVERCLAW = 0x6e, /* IBM Common Link Access to Workstn */ + STACKTOSTACK = 0x6f, /* IBM stackToStack */ + VIRTUALIPADDRESS = 0x70, /* IBM VIPA */ + MPC = 0x71, /* IBM multi-protocol channel support */ + IPOVERATM = 0x72, /* IBM ipOverAtm */ + ISO88025FIBER = 0x73, /* ISO 802.5j Fiber Token Ring */ + TDLC = 0x74, /* IBM twinaxial data link control */ + GIGABITETHERNET = 0x75, /* Gigabit Ethernet */ + HDLC = 0x76, /* HDLC */ + LAPF = 0x77, /* LAP F */ + V37 = 0x78, /* V.37 */ + X25MLP = 0x79, /* Multi-Link Protocol */ + X25HUNTGROUP = 0x7a, /* X25 Hunt Group */ + TRANSPHDLC = 0x7b, /* Transp HDLC */ + INTERLEAVE = 0x7c, /* Interleave channel */ + FAST = 0x7d, /* Fast channel */ + IP = 0x7e, /* IP (for APPN HPR in IP networks) */ + DOCSCABLEMACLAYER = 0x7f, /* CATV Mac Layer */ + DOCSCABLEDOWNSTREAM = 0x80, /* CATV Downstream interface */ + DOCSCABLEUPSTREAM = 0x81, /* CATV Upstream interface */ + A12MPPSWITCH = 0x82, /* Avalon Parallel Processor */ + TUNNEL = 0x83, /* Encapsulation interface */ + COFFEE = 0x84, /* coffee pot */ + CES = 0x85, /* Circiut Emulation Service */ + ATMSUBINTERFACE = 0x86, /* (x) ATM Sub Interface */ + L2VLAN = 0x87, /* Layer 2 Virtual LAN using 802.1Q */ + L3IPVLAN = 0x88, /* Layer 3 Virtual LAN - IP Protocol */ + L3IPXVLAN = 0x89, /* Layer 3 Virtual LAN - IPX Prot. */ + DIGITALPOWERLINE = 0x8a, /* IP over Power Lines */ + MEDIAMAILOVERIP = 0x8b, /* (xxx) Multimedia Mail over IP */ + DTM = 0x8c, /* Dynamic synchronous Transfer Mode */ + DCN = 0x8d, /* Data Communications Network */ + IPFORWARD = 0x8e, /* IP Forwarding Interface */ + MSDSL = 0x8f, /* Multi-rate Symmetric DSL */ + IEEE1394 = 0x90, /* IEEE1394 High Performance SerialBus*/ + IFGSN = 0x91, /* HIPPI-6400 */ + DVBRCCMACLAYER = 0x92, /* DVB-RCC MAC Layer */ + DVBRCCDOWNSTREAM = 0x93, /* DVB-RCC Downstream Channel */ + DVBRCCUPSTREAM = 0x94, /* DVB-RCC Upstream Channel */ + ATMVIRTUAL = 0x95, /* ATM Virtual Interface */ + MPLSTUNNEL = 0x96, /* MPLS Tunnel Virtual Interface */ + SRP = 0x97, /* Spatial Reuse Protocol */ + VOICEOVERATM = 0x98, /* Voice over ATM */ + VOICEOVERFRAMERELAY = 0x99, /* Voice Over Frame Relay */ + IDSL = 0x9a, /* Digital Subscriber Loop over ISDN */ + COMPOSITELINK = 0x9b, /* Avici Composite Link Interface */ + SS7SIGLINK = 0x9c, /* SS7 Signaling Link */ + PROPWIRELESSP2P = 0x9d, /* Prop. P2P wireless interface */ + FRFORWARD = 0x9e, /* Frame forward Interface */ + RFC1483 = 0x9f, /* Multiprotocol over ATM AAL5 */ + USB = 0xa0, /* USB Interface */ + IEEE8023ADLAG = 0xa1, /* IEEE 802.3ad Link Aggregate*/ + BGPPOLICYACCOUNTING = 0xa2, /* BGP Policy Accounting */ + FRF16MFRBUNDLE = 0xa3, /* FRF.16 Multilink Frame Relay*/ + H323GATEKEEPER = 0xa4, /* H323 Gatekeeper */ + H323PROXY = 0xa5, /* H323 Voice and Video Proxy */ + MPLS = 0xa6, /* MPLS */ + MFSIGLINK = 0xa7, /* Multi-frequency signaling link */ + HDSL2 = 0xa8, /* High Bit-Rate DSL, 2nd gen. */ + SHDSL = 0xa9, /* Multirate HDSL2 */ + DS1FDL = 0xaa, /* Facility Data Link (4Kbps) on a DS1*/ + POS = 0xab, /* Packet over SONET/SDH Interface */ + DVBASILN = 0xac, /* DVB-ASI Input */ + DVBASIOUT = 0xad, /* DVB-ASI Output */ + PLC = 0xae, /* Power Line Communications */ + NFAS = 0xaf, /* Non-Facility Associated Signaling */ + TR008 = 0xb0, /* TROO8 */ + GR303RDT = 0xb1, /* Remote Digital Terminal */ + GR303IDT = 0xb2, /* Integrated Digital Terminal */ + ISUP = 0xb3, /* ISUP */ + PROPDOCSWIRELESSMACLAYER = 0xb4, /* prop/Wireless MAC Layer */ + PROPDOCSWIRELESSDOWNSTREAM = 0xb5, /* prop/Wireless Downstream */ + PROPDOCSWIRELESSUPSTREAM = 0xb6, /* prop/Wireless Upstream */ + HIPERLAN2 = 0xb7, /* HIPERLAN Type 2 Radio Interface */ + PROPBWAP2MP = 0xb8, /* PropBroadbandWirelessAccess P2MP*/ + SONETOVERHEADCHANNEL = 0xb9, /* SONET Overhead Channel */ + DIGITALWRAPPEROVERHEADCHANNEL = 0xba, /* Digital Wrapper Overhead */ + AAL2 = 0xbb, /* ATM adaptation layer 2 */ + RADIOMAC = 0xbc, /* MAC layer over radio links */ + ATMRADIO = 0xbd, /* ATM over radio links */ + IMT = 0xbe, /* Inter-Machine Trunks */ + MVL = 0xbf, /* Multiple Virtual Lines DSL */ + REACHDSL = 0xc0, /* Long Reach DSL */ + FRDLCIENDPT = 0xc1, /* Frame Relay DLCI End Point */ + ATMVCIENDPT = 0xc2, /* ATM VCI End Point */ + OPTICALCHANNEL = 0xc3, /* Optical Channel */ + OPTICALTRANSPORT = 0xc4, /* Optical Transport */ + INFINIBAND = 0xc7, /* Infiniband */ + INFINIBANDLAG = 0xc8, /* Infiniband Link Aggregate */ + BRIDGE = 0xd1, /* Transparent bridge interface */ + STF = 0xd7, /* 6to4 interface */ + + /* + * Not based on IANA assignments. Conflicting with IANA assignments. + * We should make them negative probably. + * This requires changes to struct if_data. + */ + GIF = 0xf0, /* Generic tunnel interface */ + PVC = 0xf1, /* Unused */ + ENC = 0xf4, /* Encapsulating interface */ + PFLOG = 0xf6, /* PF packet filter logging */ + PFSYNC = 0xf7, /* PF packet filter synchronization */ + WIREGUARD = 0xf8, /* WireGuard tunnel */ +} + +// +// #include +// + +/* + * Structure of a Link-Level sockaddr: + */ +// struct sockaddr_dl +Socket_Address_Data_Link :: struct { + using _: Socket_Address_Header, + index: c.ushort, /* if != 0, system given index for interface */ + type: Interface_Type, /* interface type */ + nlen: c.uchar, /* interface name length, no trailing 0 reqd. */ + alen: c.uchar, /* link level address length */ + slen: c.uchar, /* link layer selector length */ + data: [46]c.char, /* minimum work area, can be larger; contains both if name and ll address */ +} + +// +// #include +// + +in_addr_t :: distinct u32be + +/* Internet address (a structure for historical reasons). */ +// struct in_addr +IP4_Address :: struct #raw_union { + // NOTE(Feoramund): I have modified this struct from its C definition by + // introducing the byte variant to make it easier to work with. + addr8: [4]u8, + addr32: in_addr_t, +} + +/* Socket address, internet style. */ +// struct sockaddr_in +Socket_Address_Internet :: struct #packed { + using _: Socket_Address_Header, + port: in_port_t, + addr: IP4_Address, + zero: [8]c.char, +} + +// +// #include +// + +/* + * IPv6 address + */ +// struct in6_addr +IP6_Address :: struct #raw_union { + addr8: [16]u8, + addr16: [8]u16be, + addr32: [4]u32be, +} + +/* + * Socket address for IPv6 + */ +// struct sockaddr_in6 +Socket_Address_Internet6 :: struct #packed { + using _: Socket_Address_Header, + port: in_port_t, /* Transport layer port # */ + flowinfo: u32, /* IP6 flow information */ + addr: IP6_Address, /* IP6 address */ + scope_id: u32, /* scope zone index */ +} + +// +// #include +// + +/* op code for _umtx_op */ +// #define UMTX_OP_* +Userland_Mutex_Operation :: enum c.int { + LOCK = 0, /* COMPAT10 */ + UNLOCK = 1, /* COMPAT10 */ + WAIT = 2, + WAKE = 3, + MUTEX_TRYLOCK = 4, + MUTEX_LOCK = 5, + MUTEX_UNLOCK = 6, + SET_CEILING = 7, + CV_WAIT = 8, + CV_SIGNAL = 9, + CV_BROADCAST = 10, + WAIT_UINT = 11, + RW_RDLOCK = 12, + RW_WRLOCK = 13, + RW_UNLOCK = 14, + WAIT_UINT_PRIVATE = 15, + WAKE_PRIVATE = 16, + MUTEX_WAIT = 17, + MUTEX_WAKE = 18, /* deprecated */ + SEM_WAIT = 19, /* deprecated */ + SEM_WAKE = 20, /* deprecated */ + NWAKE_PRIVATE = 21, + MUTEX_WAKE2 = 22, + SEM2_WAIT = 23, + SEM2_WAKE = 24, + SHM = 25, + ROBUST_LISTS = 26, + GET_MIN_TIMEOUT = 27, + SET_MIN_TIMEOUT = 28, +} diff --git a/core/sys/info/cpu_linux_riscv64.odin b/core/sys/info/cpu_linux_riscv64.odin new file mode 100644 index 000000000..0b64c3725 --- /dev/null +++ b/core/sys/info/cpu_linux_riscv64.odin @@ -0,0 +1,113 @@ +//+build riscv64 +//+build linux +package sysinfo + +import "base:intrinsics" + +import "core:sys/linux" + +@(init, private) +init_cpu_features :: proc() { + _features: CPU_Features + defer cpu_features = _features + + HWCAP_Bits :: enum u64 { + I = 'I' - 'A', + M = 'M' - 'A', + A = 'A' - 'A', + F = 'F' - 'A', + D = 'D' - 'A', + C = 'C' - 'A', + V = 'V' - 'A', + } + HWCAP :: bit_set[HWCAP_Bits; u64] + + // Read HWCAP for base extensions, we can get this info through hwprobe too but that is Linux 6.4+ only. + { + fd, err := linux.open("/proc/self/auxv", {}) + if err != .NONE { return } + defer linux.close(fd) + + // This is probably enough right? + buf: [4096]byte + n, rerr := linux.read(fd, buf[:]) + if rerr != .NONE || n == 0 { return } + + ulong :: u64 + AT_HWCAP :: 16 + + auxv := buf[:n] + for len(auxv) >= size_of(ulong)*2 { + key := intrinsics.unaligned_load((^ulong)(&auxv[0])) + val := intrinsics.unaligned_load((^ulong)(&auxv[size_of(ulong)])) + auxv = auxv[2*size_of(ulong):] + + if key != AT_HWCAP { + continue + } + + cap := transmute(HWCAP)(val) + if .I in cap { + _features += { .I } + } + if .M in cap { + _features += { .M } + } + if .A in cap { + _features += { .A } + } + if .F in cap { + _features += { .F } + } + if .D in cap { + _features += { .D } + } + if .C in cap { + _features += { .C } + } + if .V in cap { + _features += { .V } + } + break + } + } + + // hwprobe for other features. + { + pairs := []linux.RISCV_HWProbe{ + { key = .IMA_EXT_0 }, + { key = .CPUPERF_0 }, + { key = .MISALIGNED_SCALAR_PERF }, + } + err := linux.riscv_hwprobe(raw_data(pairs), len(pairs), 0, nil, {}) + if err != nil { + assert(err == .ENOSYS, "unexpected error from riscv_hwprobe()") + return + } + + assert(pairs[0].key == .IMA_EXT_0) + exts := pairs[0].value.ima_ext_0 + exts -= { .FD, .C, .V } + _features += transmute(CPU_Features)exts + + if pairs[2].key == .MISALIGNED_SCALAR_PERF { + if pairs[2].value.misaligned_scalar_perf == .FAST { + _features += { .Misaligned_Supported, .Misaligned_Fast } + } else if pairs[2].value.misaligned_scalar_perf != .UNSUPPORTED { + _features += { .Misaligned_Supported } + } + } else { + assert(pairs[1].key == .CPUPERF_0) + if .FAST in pairs[1].value.cpu_perf_0 { + _features += { .Misaligned_Supported, .Misaligned_Fast } + } else if .UNSUPPORTED not_in pairs[1].value.cpu_perf_0 { + _features += { .Misaligned_Supported } + } + } + } +} + +@(init, private) +init_cpu_name :: proc() { + cpu_name = "RISCV64" +} diff --git a/core/sys/info/cpu_riscv64.odin b/core/sys/info/cpu_riscv64.odin new file mode 100644 index 000000000..c3319c48c --- /dev/null +++ b/core/sys/info/cpu_riscv64.odin @@ -0,0 +1,100 @@ +package sysinfo + +CPU_Feature :: enum u64 { + // Bit-Manipulation ISA Extensions v1. + Zba = 3, + Zbb, + Zbs, + + // CMOs (ratified). + Zicboz, + + // Bit-Manipulation ISA Extensions v1. + Zbc, + + // Scalar Crypto ISA extensions v1. + Zbkb, + Zbkc, + Zbkx, + Zknd, + Zkne, + Zknh, + Zksed, + Zksh, + Zkt, + + // Cryptography Extensions Volume II v1. + Zvbb, + Zvbc, + Zvkb, + Zvkg, + Zvkned, + Zvknha, + Zvknhb, + Zvksed, + Zvksh, + Zvkt, + + // ISA Manual v1. + Zfh, + Zfhmin, + Zihintntl, + + // ISA manual (ratified). + Zvfh, + Zvfhmin, + Zfa, + Ztso, + + // Atomic Compare-and-Swap Instructions Manual (ratified). + Zacas, + Zicond, + + // ISA manual (ratified). + Zihintpause, + + // Vector Extensions Manual v1. + Zve32x, + Zve32f, + Zve64x, + Zve64f, + Zve64d, + + // ISA manual (ratified). + Zimop, + + // Code Size Reduction (ratified). + Zca, + Zcb, + Zcd, + Zcf, + + // ISA manual (ratified). + Zcmop, + Zawrs, + + // Base features, don't think this is ever not here. + I, + // Integer multiplication and division, currently required by Odin. + M, + // Atomics. + A, + // Single precision floating point, currently required by Odin. + F, + // Double precision floating point, currently required by Odin. + D, + // Compressed instructions. + C, + // Vector operations. + V, + + // Indicates Misaligned Scalar Loads will not trap the program. + Misaligned_Supported, + // Indicates Hardware Support for Misaligned Scalar Loads. + Misaligned_Fast, +} + +CPU_Features :: distinct bit_set[CPU_Feature; u64] + +cpu_features: Maybe(CPU_Features) +cpu_name: Maybe(string) diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 802cd9c60..b5cd62d81 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -2,6 +2,12 @@ Copyright 2022 Jeroen van Rijn . Made available under Odin's BSD-3 license. +List of contributors: + Jeroen van Rijn: Initial implementation. + Laytan: ARM and RISC-V CPU feature detection. +*/ + +/* Package `core:sys/info` gathers system information on: Windows, Linux, macOS, FreeBSD & OpenBSD. @@ -11,9 +17,10 @@ and CPU information. On Windows, GPUs will also be enumerated using the registry. CPU feature flags can be tested against `cpu_features`, where applicable, e.g. -`if .aes in si.aes { ... }` +`if .aes in info.cpu_features.? { ... }` Example: + package main import "core:fmt" import si "core:sys/info" diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index 0cae0aa98..3fd857bfe 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -528,6 +528,8 @@ macos_release_map: map[string]Darwin_To_Release = { "23E214" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}}, "23E224" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}}, "23F79" = {{23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, + "23G80" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}}, + "23G93" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}}, } @(private) diff --git a/core/sys/info/sysinfo.odin b/core/sys/info/sysinfo.odin index f0262f317..f624a1718 100644 --- a/core/sys/info/sysinfo.odin +++ b/core/sys/info/sysinfo.odin @@ -1,6 +1,6 @@ package sysinfo -when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64) { +when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64) { #assert(false, "This package is unsupported on this architecture.") } diff --git a/core/sys/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin new file mode 100644 index 000000000..27d1ecaae --- /dev/null +++ b/core/sys/kqueue/kqueue.odin @@ -0,0 +1,256 @@ +//+build darwin, netbsd, openbsd, freebsd +package kqueue + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +import "base:intrinsics" + +import "core:c" +import "core:sys/posix" + +KQ :: posix.FD + +kqueue :: proc() -> (kq: KQ, err: posix.Errno) { + kq = _kqueue() + if kq == -1 { + err = posix.errno() + } + return +} + +kevent :: proc(kq: KQ, change_list: []KEvent, event_list: []KEvent, timeout: ^posix.timespec) -> (n_events: c.int, err: posix.Errno) { + n_events = _kevent( + kq, + raw_data(change_list), + c.int(len(change_list)), + raw_data(event_list), + c.int(len(event_list)), + timeout, + ) + if n_events == -1 { + err = posix.errno() + } + return +} + +Flag :: enum _Flags_Backing { + Add = log2(0x0001), // Add event to kq (implies .Enable). + Delete = log2(0x0002), // Delete event from kq. + Enable = log2(0x0004), // Enable event. + Disable = log2(0x0008), // Disable event (not reported). + One_Shot = log2(0x0010), // Only report one occurrence. + Clear = log2(0x0020), // Clear event state after reporting. + Receipt = log2(0x0040), // Force immediate event output. + Dispatch = log2(0x0080), // Disable event after reporting. + + Error = log2(0x4000), // Error, data contains errno. + EOF = log2(0x8000), // EOF detected. +} +Flags :: bit_set[Flag; _Flags_Backing] + +Filter :: enum _Filter_Backing { + Read = _FILTER_READ, // Check for read availability on the file descriptor. + Write = _FILTER_WRITE, // Check for write availability on the file descriptor. + AIO = _FILTER_AIO, // Attached to AIO requests. + VNode = _FILTER_VNODE, // Check for changes to the subject file. + Proc = _FILTER_PROC, // Check for changes to the subject process. + Signal = _FILTER_SIGNAL, // Check for signals delivered to the process. + Timer = _FILTER_TIMER, // Timers. +} + +RW_Flag :: enum u32 { + Low_Water_Mark = log2(0x00000001), +} +RW_Flags :: bit_set[RW_Flag; u32] + +VNode_Flag :: enum u32 { + Delete = log2(0x00000001), // Deleted. + Write = log2(0x00000002), // Contents changed. + Extend = log2(0x00000004), // Size increased. + Attrib = log2(0x00000008), // Attributes changed. + Link = log2(0x00000010), // Link count changed. + Rename = log2(0x00000020), // Renamed. + Revoke = log2(0x00000040), // Access was revoked. +} +VNode_Flags :: bit_set[VNode_Flag; u32] + +Proc_Flag :: enum u32 { + Exit = log2(0x80000000), // Process exited. + Fork = log2(0x40000000), // Process forked. + Exec = log2(0x20000000), // Process exec'd. + Signal = log2(0x08000000), // Shared with `Filter.Signal`. +} +Proc_Flags :: bit_set[Proc_Flag; u32] + +Timer_Flag :: enum u32 { + Seconds = log2(0x00000001), // Data is seconds. + USeconds = log2(0x00000002), // Data is microseconds. + NSeconds = log2(_NOTE_NSECONDS), // Data is nanoseconds. + Absolute = log2(_NOTE_ABSOLUTE), // Absolute timeout. +} +Timer_Flags :: bit_set[Timer_Flag; u32] + +when ODIN_OS == .Darwin { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000004 + _NOTE_ABSOLUTE :: 0x00000008 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: c.long /* intptr_t */, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + } + +} else when ODIN_OS == .FreeBSD { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000004 + _NOTE_ABSOLUTE :: 0x00000008 + + KEvent :: struct { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + // Extensions. + ext: [4]u64, + } +} else when ODIN_OS == .NetBSD { + + _Filter_Backing :: distinct u32 + _Flags_Backing :: distinct u32 + + _FILTER_READ :: 0 + _FILTER_WRITE :: 1 + _FILTER_AIO :: 2 + _FILTER_VNODE :: 3 + _FILTER_PROC :: 4 + _FILTER_SIGNAL :: 5 + _FILTER_TIMER :: 6 + + _NOTE_NSECONDS :: 0x00000003 + _NOTE_ABSOLUTE :: 0x00000010 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + // Extensions. + ext: [4]u64, + } +} else when ODIN_OS == .OpenBSD { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000003 + _NOTE_ABSOLUTE :: 0x00000010 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + } +} + +@(private) +log2 :: intrinsics.constant_log2 + +foreign lib { + @(link_name="kqueue") + _kqueue :: proc() -> KQ --- + @(link_name="kevent") + _kevent :: proc(kq: KQ, change_list: [^]KEvent, n_changes: c.int, event_list: [^]KEvent, n_events: c.int, timeout: ^posix.timespec) -> c.int --- +} diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index e10edf558..8a4a6dd7a 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -983,6 +983,20 @@ Sig_Action_Flag :: enum u32 { RESETHAND = 31, } +/* + Translation of code in Sig_Info for when signo is SIGCHLD +*/ +Sig_Child_Code :: enum { + NONE, + EXITED, + KILLED, + DUMPED, + TRAPPED, + STOPPED, + CONTINUED, +} + + /* Type of socket to create - For TCP you want to use SOCK_STREAM @@ -1329,14 +1343,16 @@ Socket_Option :: enum { RESERVE_MEM = 73, TXREHASH = 74, RCVMARK = 75, - // Hardcoded 64-bit Time. It's time to move on. - TIMESTAMP = TIMESTAMP_NEW, - TIMESTAMPNS = TIMESTAMPNS_NEW, - TIMESTAMPING = TIMESTAMPING_NEW, - RCVTIMEO = RCVTIMEO_NEW, - SNDTIMEO = SNDTIMEO_NEW, + TIMESTAMP = TIMESTAMP_OLD when _SOCKET_OPTION_OLD else TIMESTAMP_NEW, + TIMESTAMPNS = TIMESTAMPNS_OLD when _SOCKET_OPTION_OLD else TIMESTAMPNS_NEW, + TIMESTAMPING = TIMESTAMPING_OLD when _SOCKET_OPTION_OLD else TIMESTAMPING_NEW, + RCVTIMEO = RCVTIMEO_OLD when _SOCKET_OPTION_OLD else RCVTIMEO_NEW, + SNDTIMEO = SNDTIMEO_OLD when _SOCKET_OPTION_OLD else SNDTIMEO_NEW, } +@(private) +_SOCKET_OPTION_OLD :: size_of(rawptr) == 8 /* || size_of(time_t) == size_of(__kernel_long_t) */ + Socket_UDP_Option :: enum { CORK = 1, ENCAP = 100, @@ -1823,3 +1839,87 @@ Execveat_Flags_Bits :: enum { AT_SYMLINK_NOFOLLOW = 8, AT_EMPTY_PATH = 12, } + +RISCV_HWProbe_Key :: enum i64 { + UNSUPPORTED = -1, + MVENDORID = 0, + MARCHID = 1, + MIMPID = 2, + BASE_BEHAVIOR = 3, + IMA_EXT_0 = 4, + // Deprecated, try `.MISALIGNED_SCALAR_PERF` first, if that is `.UNSUPPORTED`, use this. + CPUPERF_0 = 5, + ZICBOZ_BLOCK_SIZE = 6, + HIGHEST_VIRT_ADDRESS = 7, + TIME_CSR_FREQ = 8, + MISALIGNED_SCALAR_PERF = 9, +} + +RISCV_HWProbe_Flags_Bits :: enum { + WHICH_CPUS, +} + +RISCV_HWProbe_Base_Behavior_Bits :: enum { + IMA, +} + +RISCV_HWProbe_IMA_Ext_0_Bits :: enum { + FD, + C, + V, + EXT_ZBA, + EXT_ZBB, + EXT_ZBS, + EXT_ZICBOZ, + EXT_ZBC, + EXT_ZBKB, + EXT_ZBKC, + EXT_ZBKX, + EXT_ZKND, + EXT_ZKNE, + EXT_ZKNH, + EXT_ZKSED, + EXT_ZKSH, + EXT_ZKT, + EXT_ZVBB, + EXT_ZVBC, + EXT_ZVKB, + EXT_ZVKG, + EXT_ZVKNED, + EXT_ZVKNHA, + EXT_ZVKNHB, + EXT_ZVKSED, + EXT_ZVKSH, + EXT_ZVKT, + EXT_ZFH, + EXT_ZFHMIN, + EXT_ZIHINTNTL, + EXT_ZVFH, + EXT_ZVFHMIN, + EXT_ZFA, + EXT_ZTSO, + EXT_ZACAS, + EXT_ZICOND, + EXT_ZIHINTPAUSE, + EXT_ZVE32X, + EXT_ZVE32F, + EXT_ZVE64X, + EXT_ZVE64F, + EXT_ZVE64D, + EXT_ZIMOP, + EXT_ZCA, + EXT_ZCB, + EXT_ZCD, + EXT_ZCF, + EXT_ZCMOP, + EXT_ZAWRS, +} + +RISCV_HWProbe_Misaligned_Scalar_Perf :: enum { + UNKNOWN, + EMULATED, + SLOW, + FAST, + UNSUPPORTED, +} + diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index f3e9f5ff9..129444d0f 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -1,5 +1,20 @@ package linux +/* + Standard input file descriptor +*/ +STDIN_FILENO :: Fd(0) + +/* + Standard output file descriptor +*/ +STDOUT_FILENO :: Fd(1) + +/* + Standard error file descriptor +*/ +STDERR_FILENO :: Fd(2) + /* Special file descriptor to pass to `*at` functions to specify that relative paths are relative to current directory. diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index ec7357c48..5ee07a93d 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -39,7 +39,7 @@ write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { On ARM64 available since Linux 2.6.16. */ open :: proc "contextless" (name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_openat, AT_FDCWD, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) return errno_unwrap(ret, Fd) } else { @@ -68,7 +68,7 @@ close :: proc "contextless" (fd: Fd) -> (Errno) { */ stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { when size_of(int) == 8 { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat, 0) return Errno(-ret) } else { @@ -111,7 +111,7 @@ fstat :: proc "contextless" (fd: Fd, stat: ^Stat) -> (Errno) { */ lstat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { when size_of(int) == 8 { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return fstatat(AT_FDCWD, filename, stat, {.SYMLINK_NOFOLLOW}) } else { ret := syscall(SYS_lstat, cast(rawptr) filename, stat) @@ -128,7 +128,7 @@ lstat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { Available since Linux 2.2. */ poll :: proc "contextless" (fds: []Poll_Fd, timeout: i32) -> (i32, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { seconds := cast(uint) timeout / 1000 nanoseconds := cast(uint) (timeout % 1000) * 1_000_000 timeout_spec := Time_Spec{seconds, nanoseconds} @@ -232,7 +232,18 @@ rt_sigprocmask :: proc "contextless" (mask_kind: Sig_Mask_Kind, new_set: ^Sig_Se return Errno(-ret) } -// TODO(flysand): ioctl +/* + Control devices. The ioctl syscall is a bit special because + its argument is usually a pointer to some driver-specific structure. + The request value is device-specific. Consult your LibC implementation's + ioctls.h file to learn more. The returned value of ioctl *may* be an error + code value instead of a memory address depending on the request type. + Available since Linux 1.0. +*/ +ioctl :: proc "contextless" (fd: Fd, request: u32, arg: uintptr) -> (uintptr) { + ret := syscall(SYS_ioctl, fd, request, arg) + return uintptr(ret) +} /* Read the file at a specified offset. @@ -279,13 +290,13 @@ writev :: proc "contextless" (fd: Fd, iov: []IO_Vec) -> (int, Errno) { Available since Linux 1.0. For ARM64 available since Linux 2.6.16. */ -access :: proc "contextless" (name: cstring, mode: Mode = F_OK) -> (bool, Errno) { - when ODIN_ARCH == .arm64 { +access :: proc "contextless" (name: cstring, mode: Mode = F_OK) -> (Errno) { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_faccessat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode) - return errno_unwrap(ret, bool) + return Errno(-ret) } else { ret := syscall(SYS_access, cast(rawptr) name, transmute(u32) mode) - return errno_unwrap(ret, bool) + return Errno(-ret) } } @@ -396,7 +407,7 @@ dup :: proc "contextless" (fd: Fd) -> (Fd, Errno) { On ARM64 available since Linux 2.6.27. */ dup2 :: proc "contextless" (old: Fd, new: Fd) -> (Fd, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_dup3, old, new, 0) return errno_unwrap(ret, Fd) } else { @@ -411,7 +422,7 @@ dup2 :: proc "contextless" (old: Fd, new: Fd) -> (Fd, Errno) { On ARM64 available since Linux 2.6.16. */ pause :: proc "contextless" () { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { syscall(SYS_ppoll, 0, 0, 0, 0) } else { syscall(SYS_pause) @@ -441,7 +452,7 @@ getitimer :: proc "contextless" (which: ITimer_Which, cur: ^ITimer_Val) -> (Errn Available since Linux 1.0. */ alarm :: proc "contextless" (seconds: u32) -> u32 { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { new := ITimer_Val { value = { seconds = cast(int) seconds } } old := ITimer_Val {} syscall(SYS_setitimer, ITimer_Which.REAL, &new, &old) @@ -754,7 +765,7 @@ getsockopt :: proc { Available since Linux 1.0. */ fork :: proc "contextless" () -> (Pid, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0)) return errno_unwrap(ret, Pid) } else { @@ -768,7 +779,7 @@ fork :: proc "contextless" () -> (Pid, Errno) { Available since Linux 2.2. */ vfork :: proc "contextless" () -> Pid { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return Pid(syscall(SYS_vfork)) } else { return Pid(syscall(SYS_clone, Signal.SIGCHLD)) @@ -781,7 +792,7 @@ vfork :: proc "contextless" () -> Pid { On ARM64 available since Linux 3.19. */ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) -> (Errno) { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { @@ -1182,7 +1193,7 @@ fchdir :: proc "contextless" (fd: Fd) -> (Errno) { On ARM64 available since Linux 2.6.16. */ rename :: proc "contextless" (old: cstring, new: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_renameat, AT_FDCWD, cast(rawptr) old, AT_FDCWD, cast(rawptr) new) return Errno(-ret) } else { @@ -1197,7 +1208,7 @@ rename :: proc "contextless" (old: cstring, new: cstring) -> (Errno) { On ARM64 available since Linux 2.6.16. */ mkdir :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_mkdirat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode) return Errno(-ret) } else { @@ -1212,7 +1223,7 @@ mkdir :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { On ARM64 available since Linux 2.6.16. */ rmdir :: proc "contextless" (name: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_unlinkat, AT_FDCWD, cast(rawptr) name, transmute(i32) FD_Flags{.REMOVEDIR}) return Errno(-ret) } else { @@ -1227,7 +1238,7 @@ rmdir :: proc "contextless" (name: cstring) -> (Errno) { On ARM64 available since Linux 2.6.16. */ creat :: proc "contextless" (name: cstring, mode: Mode) -> (Fd, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return openat(AT_FDCWD, name, {.CREAT, .WRONLY,.TRUNC}, mode) } else { ret := syscall(SYS_creat, cast(rawptr) name, transmute(u32) mode) @@ -1241,7 +1252,7 @@ creat :: proc "contextless" (name: cstring, mode: Mode) -> (Fd, Errno) { On ARM64 available since Linux 2.6.16. */ link :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath, 0) return Errno(-ret) } else { @@ -1256,7 +1267,7 @@ link :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { On ARM64 available since Linux 2.6.16. */ unlink :: proc "contextless" (name: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_unlinkat, AT_FDCWD, cast(rawptr) name, 0) return Errno(-ret) } else { @@ -1271,7 +1282,7 @@ unlink :: proc "contextless" (name: cstring) -> (Errno) { On arm64 available since Linux 2.6.16. */ symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_symlinkat, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath) return Errno(-ret) } else { @@ -1286,7 +1297,7 @@ symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { On arm64 available since Linux 2.6.16. */ readlink :: proc "contextless" (name: cstring, buf: []u8) -> (int, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_readlinkat, AT_FDCWD, cast(rawptr) name, raw_data(buf), len(buf)) return errno_unwrap(ret, int) } else { @@ -1301,7 +1312,7 @@ readlink :: proc "contextless" (name: cstring, buf: []u8) -> (int, Errno) { On ARM64 available since Linux 2.6.16. */ chmod :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_fchmodat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode) return Errno(-ret) } else { @@ -1329,7 +1340,7 @@ chown :: proc "contextless" (name: cstring, uid: Uid, gid: Gid) -> (Errno) { when size_of(int) == 4 { ret := syscall(SYS_chown32, cast(rawptr) name, uid, gid) return Errno(-ret) - } else when ODIN_ARCH == .arm64 { + } else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_fchownat, AT_FDCWD, cast(rawptr) name, uid, gid, 0) return Errno(-ret) } else { @@ -1363,7 +1374,7 @@ lchown :: proc "contextless" (name: cstring, uid: Uid, gid: Gid) -> (Errno) { when size_of(int) == 4 { ret := syscall(SYS_lchown32, cast(rawptr) name, uid, gid) return Errno(-ret) - } else when ODIN_ARCH == .arm64 { + } else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_fchownat, AT_FDCWD, cast(rawptr) name, uid, gid, transmute(i32) FD_Flags{.SYMLINK_NOFOLLOW}) return Errno(-ret) } else { @@ -1716,7 +1727,7 @@ getppid :: proc "contextless" () -> Pid { Available since Linux 1.0. */ getpgrp :: proc "contextless" () -> (Pid, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_getpgid, 0) return errno_unwrap(ret, Pid) } else { @@ -1939,7 +1950,7 @@ sigaltstack :: proc "contextless" (stack: ^Sig_Stack, old_stack: ^Sig_Stack) -> On ARM64 available since Linux 2.6.16. */ mknod :: proc "contextless" (name: cstring, mode: Mode, dev: Dev) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_mknodat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode, dev) return Errno(-ret) } else { @@ -2196,7 +2207,7 @@ gettid :: proc "contextless" () -> Pid { Available since Linux 1.0. */ time :: proc "contextless" (tloc: ^uint) -> (Errno) { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { ret := syscall(SYS_time, tloc) return Errno(-ret) } else { @@ -2324,7 +2335,7 @@ futex :: proc{ Available since Linux 2.6. */ epoll_create :: proc(size: i32 = 1) -> (Fd, Errno) { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { ret := syscall(SYS_epoll_create, i32(1)) return errno_unwrap(ret, Fd) } else { @@ -2393,17 +2404,44 @@ timer_delete :: proc "contextless" (timer: Timer) -> (Errno) { return Errno(-ret) } -// TODO(flysand): clock_settime +/* + Set the time of the specified clock. + Available since Linux 2.6. +*/ +clock_settime :: proc "contextless" (clock: Clock_Id, ts: ^Time_Spec) -> (Errno) { + ret := syscall(SYS_clock_settime, clock, ts) + return Errno(-ret) +} +/* + Retrieve the time of the specified clock. + Available since Linux 2.6. +*/ clock_gettime :: proc "contextless" (clock: Clock_Id) -> (ts: Time_Spec, err: Errno) { ret := syscall(SYS_clock_gettime, clock, &ts) err = Errno(-ret) return } -// TODO(flysand): clock_getres -// TODO(flysand): clock_nanosleep +/* + Finds the resolution of the specified clock. + Available since Linux 2.6. +*/ +clock_getres :: proc "contextless" (clock: Clock_Id) -> (res: Time_Spec, err: Errno) { + ret := syscall(SYS_clock_getres, clock, &res) + err = Errno(-ret) + return +} + +/* + Sleep for an interval specified with nanosecond precision. + Available since Linux 2.6. +*/ +clock_nanosleep :: proc "contextless" (clock: Clock_Id, flags: ITimer_Flags, request: ^Time_Spec, remain: ^Time_Spec) -> (Errno) { + ret := syscall(SYS_clock_nanosleep, clock, transmute(u32) flags, request, remain) + return Errno(-ret) +} /* Exit the thread group. @@ -2422,7 +2460,7 @@ exit_group :: proc "contextless" (code: i32) -> ! { Available since Linux 2.6. */ epoll_wait :: proc(epfd: Fd, events: [^]EPoll_Event, count: i32, timeout: i32) -> (i32, Errno) { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { ret := syscall(SYS_epoll_wait, epfd, events, count, timeout) return errno_unwrap(ret, i32) } else { @@ -2616,9 +2654,9 @@ fchmodat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode, flags: FD_ Checks the user permissions for a file at specified dirfd. Available since Linux 2.6.16. */ -faccessat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK) -> (bool, Errno) { +faccessat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK) -> (Errno) { ret := syscall(SYS_faccessat, dirfd, cast(rawptr) name, transmute(u32) mode) - return errno_unwrap(ret, bool) + return Errno(-ret) } /* @@ -2916,9 +2954,9 @@ pidfd_getfd :: proc "contextless" (pidfd: Pid_FD, fd: Fd, flags: i32 = 0) -> (Fd Checks the user permissions for a file at specified dirfd (with flags). Available since Linux 5.8. */ -faccessat2 :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK, flags: FD_Flags = FD_Flags{}) -> (bool, Errno) { +faccessat2 :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK, flags: FD_Flags = FD_Flags{}) -> (Errno) { ret := syscall(SYS_faccessat2, dirfd, cast(rawptr) name, transmute(u32) mode, transmute(i32) flags) - return errno_unwrap(ret, bool) + return Errno(-ret) } // TODO(flysand): process_madvise @@ -2955,3 +2993,18 @@ epoll_pwait2 :: proc(epfd: Fd, events: [^]EPoll_Event, count: i32, timeout: ^Tim // TODO(flysand): fchmodat2 // TODO(flysand): map_shadow_stack + +when ODIN_ARCH == .riscv64 { + /* + Probe for RISC-V Hardware Support. + Available since Linux 6.4. + + TODO: cpu_set_t + + See: https://docs.kernel.org/arch/riscv/hwprobe.html + */ + riscv_hwprobe :: proc "contextless" (pairs: [^]RISCV_HWProbe, pair_count: uint, cpu_count: uint, cpus: rawptr /* cpu_set_t */, flags: RISCV_HWProbe_Flags) -> Errno { + ret := syscall(SYS_riscv_hwprobe, pairs, pair_count, cpu_count, cpus, transmute(u32)flags) + return Errno(-ret) + } +} diff --git a/core/sys/linux/syscall_riscv64.odin b/core/sys/linux/syscall_riscv64.odin new file mode 100644 index 000000000..d2fd0c2ff --- /dev/null +++ b/core/sys/linux/syscall_riscv64.odin @@ -0,0 +1,335 @@ +//+build riscv64 +package linux + +// https://github.com/riscv-collab/riscv-gnu-toolchain/blob/master/linux-headers/include/asm-generic/unistd.h + +SYS_io_setup :: uintptr(0) +SYS_io_destroy :: uintptr(1) +SYS_io_submit :: uintptr(2) +SYS_io_cancel :: uintptr(3) +SYS_io_getevents :: uintptr(4) +SYS_setxattr :: uintptr(5) +SYS_lsetxattr :: uintptr(6) +SYS_fsetxattr :: uintptr(7) +SYS_getxattr :: uintptr(8) +SYS_lgetxattr :: uintptr(9) +SYS_fgetxattr :: uintptr(10) +SYS_listxattr :: uintptr(11) +SYS_llistxattr :: uintptr(12) +SYS_flistxattr :: uintptr(13) +SYS_removexattr :: uintptr(14) +SYS_lremovexattr :: uintptr(15) +SYS_fremovexattr :: uintptr(16) +SYS_getcwd :: uintptr(17) +SYS_lookup_dcookie :: uintptr(18) +SYS_eventfd2 :: uintptr(19) +SYS_epoll_create1 :: uintptr(20) +SYS_epoll_ctl :: uintptr(21) +SYS_epoll_pwait :: uintptr(22) +SYS_dup :: uintptr(23) +SYS_dup3 :: uintptr(24) +SYS_fcntl :: uintptr(25) +SYS_inotify_init1 :: uintptr(26) +SYS_inotify_add_watch :: uintptr(27) +SYS_inotify_rm_watch :: uintptr(28) +SYS_ioctl :: uintptr(29) +SYS_ioprio_set :: uintptr(30) +SYS_ioprio_get :: uintptr(31) +SYS_flock :: uintptr(32) +SYS_mknodat :: uintptr(33) +SYS_mkdirat :: uintptr(34) +SYS_unlinkat :: uintptr(35) +SYS_symlinkat :: uintptr(36) +SYS_linkat :: uintptr(37) +SYS_renameat :: uintptr(38) +SYS_umount2 :: uintptr(39) +SYS_mount :: uintptr(40) +SYS_pivot_root :: uintptr(41) +SYS_nfsservctl :: uintptr(42) +SYS_statfs :: uintptr(43) +SYS_fstatfs :: uintptr(44) +SYS_truncate :: uintptr(45) +SYS_ftruncate :: uintptr(46) +SYS_fallocate :: uintptr(47) +SYS_faccessat :: uintptr(48) +SYS_chdir :: uintptr(49) +SYS_fchdir :: uintptr(50) +SYS_chroot :: uintptr(51) +SYS_fchmod :: uintptr(52) +SYS_fchmodat :: uintptr(53) +SYS_fchownat :: uintptr(54) +SYS_fchown :: uintptr(55) +SYS_openat :: uintptr(56) +SYS_close :: uintptr(57) +SYS_vhangup :: uintptr(58) +SYS_pipe2 :: uintptr(59) +SYS_quotactl :: uintptr(60) +SYS_getdents64 :: uintptr(61) +SYS_lseek :: uintptr(62) +SYS_read :: uintptr(63) +SYS_write :: uintptr(64) +SYS_readv :: uintptr(65) +SYS_writev :: uintptr(66) +SYS_pread64 :: uintptr(67) +SYS_pwrite64 :: uintptr(68) +SYS_preadv :: uintptr(69) +SYS_pwritev :: uintptr(70) +SYS_sendfile :: uintptr(71) +SYS_pselect6 :: uintptr(72) +SYS_ppoll :: uintptr(73) +SYS_signalfd4 :: uintptr(74) +SYS_vmsplice :: uintptr(75) +SYS_splice :: uintptr(76) +SYS_tee :: uintptr(77) +SYS_readlinkat :: uintptr(78) +SYS_fstatat :: uintptr(79) +SYS_fstat :: uintptr(80) +SYS_sync :: uintptr(81) +SYS_fsync :: uintptr(82) +SYS_fdatasync :: uintptr(83) +SYS_sync_file_range2 :: uintptr(84) +SYS_sync_file_range :: uintptr(84) +SYS_timerfd_create :: uintptr(85) +SYS_timerfd_settime :: uintptr(86) +SYS_timerfd_gettime :: uintptr(87) +SYS_utimensat :: uintptr(88) +SYS_acct :: uintptr(89) +SYS_capget :: uintptr(90) +SYS_capset :: uintptr(91) +SYS_personality :: uintptr(92) +SYS_exit :: uintptr(93) +SYS_exit_group :: uintptr(94) +SYS_waitid :: uintptr(95) +SYS_set_tid_address :: uintptr(96) +SYS_unshare :: uintptr(97) +SYS_futex :: uintptr(98) +SYS_set_robust_list :: uintptr(99) +SYS_get_robust_list :: uintptr(100) +SYS_nanosleep :: uintptr(101) +SYS_getitimer :: uintptr(102) +SYS_setitimer :: uintptr(103) +SYS_kexec_load :: uintptr(104) +SYS_init_module :: uintptr(105) +SYS_delete_module :: uintptr(106) +SYS_timer_create :: uintptr(107) +SYS_timer_gettime :: uintptr(108) +SYS_timer_getoverrun :: uintptr(109) +SYS_timer_settime :: uintptr(110) +SYS_timer_delete :: uintptr(111) +SYS_clock_settime :: uintptr(112) +SYS_clock_gettime :: uintptr(113) +SYS_clock_getres :: uintptr(114) +SYS_clock_nanosleep :: uintptr(115) +SYS_syslog :: uintptr(116) +SYS_ptrace :: uintptr(117) +SYS_sched_setparam :: uintptr(118) +SYS_sched_setscheduler :: uintptr(119) +SYS_sched_getscheduler :: uintptr(120) +SYS_sched_getparam :: uintptr(121) +SYS_sched_setaffinity :: uintptr(122) +SYS_sched_getaffinity :: uintptr(123) +SYS_sched_yield :: uintptr(124) +SYS_sched_get_priority_max :: uintptr(125) +SYS_sched_get_priority_min :: uintptr(126) +SYS_sched_rr_get_interval :: uintptr(127) +SYS_restart_syscall :: uintptr(128) +SYS_kill :: uintptr(129) +SYS_tkill :: uintptr(130) +SYS_tgkill :: uintptr(131) +SYS_sigaltstack :: uintptr(132) +SYS_rt_sigsuspend :: uintptr(133) +SYS_rt_sigaction :: uintptr(134) +SYS_rt_sigprocmask :: uintptr(135) +SYS_rt_sigpending :: uintptr(136) +SYS_rt_sigtimedwait :: uintptr(137) +SYS_rt_sigqueueinfo :: uintptr(138) +SYS_rt_sigreturn :: uintptr(139) +SYS_setpriority :: uintptr(140) +SYS_getpriority :: uintptr(141) +SYS_reboot :: uintptr(142) +SYS_setregid :: uintptr(143) +SYS_setgid :: uintptr(144) +SYS_setreuid :: uintptr(145) +SYS_setuid :: uintptr(146) +SYS_setresuid :: uintptr(147) +SYS_getresuid :: uintptr(148) +SYS_setresgid :: uintptr(149) +SYS_getresgid :: uintptr(150) +SYS_setfsuid :: uintptr(151) +SYS_setfsgid :: uintptr(152) +SYS_times :: uintptr(153) +SYS_setpgid :: uintptr(154) +SYS_getpgid :: uintptr(155) +SYS_getsid :: uintptr(156) +SYS_setsid :: uintptr(157) +SYS_getgroups :: uintptr(158) +SYS_setgroups :: uintptr(159) +SYS_uname :: uintptr(160) +SYS_sethostname :: uintptr(161) +SYS_setdomainname :: uintptr(162) +SYS_getrlimit :: uintptr(163) +SYS_setrlimit :: uintptr(164) +SYS_getrusage :: uintptr(165) +SYS_umask :: uintptr(166) +SYS_prctl :: uintptr(167) +SYS_getcpu :: uintptr(168) +SYS_gettimeofday :: uintptr(169) +SYS_settimeofday :: uintptr(170) +SYS_adjtimex :: uintptr(171) +SYS_getpid :: uintptr(172) +SYS_getppid :: uintptr(173) +SYS_getuid :: uintptr(174) +SYS_geteuid :: uintptr(175) +SYS_getgid :: uintptr(176) +SYS_getegid :: uintptr(177) +SYS_gettid :: uintptr(178) +SYS_sysinfo :: uintptr(179) +SYS_mq_open :: uintptr(180) +SYS_mq_unlink :: uintptr(181) +SYS_mq_timedsend :: uintptr(182) +SYS_mq_timedreceive :: uintptr(183) +SYS_mq_notify :: uintptr(184) +SYS_mq_getsetattr :: uintptr(185) +SYS_msgget :: uintptr(186) +SYS_msgctl :: uintptr(187) +SYS_msgrcv :: uintptr(188) +SYS_msgsnd :: uintptr(189) +SYS_semget :: uintptr(190) +SYS_semctl :: uintptr(191) +SYS_semtimedop :: uintptr(192) +SYS_semop :: uintptr(193) +SYS_shmget :: uintptr(194) +SYS_shmctl :: uintptr(195) +SYS_shmat :: uintptr(196) +SYS_shmdt :: uintptr(197) +SYS_socket :: uintptr(198) +SYS_socketpair :: uintptr(199) +SYS_bind :: uintptr(200) +SYS_listen :: uintptr(201) +SYS_accept :: uintptr(202) +SYS_connect :: uintptr(203) +SYS_getsockname :: uintptr(204) +SYS_getpeername :: uintptr(205) +SYS_sendto :: uintptr(206) +SYS_recvfrom :: uintptr(207) +SYS_setsockopt :: uintptr(208) +SYS_getsockopt :: uintptr(209) +SYS_shutdown :: uintptr(210) +SYS_sendmsg :: uintptr(211) +SYS_recvmsg :: uintptr(212) +SYS_readahead :: uintptr(213) +SYS_brk :: uintptr(214) +SYS_munmap :: uintptr(215) +SYS_mremap :: uintptr(216) +SYS_add_key :: uintptr(217) +SYS_request_key :: uintptr(218) +SYS_keyctl :: uintptr(219) +SYS_clone :: uintptr(220) +SYS_execve :: uintptr(221) +SYS_mmap :: uintptr(222) +SYS_fadvise64 :: uintptr(223) +SYS_swapon :: uintptr(224) +SYS_swapoff :: uintptr(225) +SYS_mprotect :: uintptr(226) +SYS_msync :: uintptr(227) +SYS_mlock :: uintptr(228) +SYS_munlock :: uintptr(229) +SYS_mlockall :: uintptr(230) +SYS_munlockall :: uintptr(231) +SYS_mincore :: uintptr(232) +SYS_madvise :: uintptr(233) +SYS_remap_file_pages :: uintptr(234) +SYS_mbind :: uintptr(235) +SYS_get_mempolicy :: uintptr(236) +SYS_set_mempolicy :: uintptr(237) +SYS_migrate_pages :: uintptr(238) +SYS_move_pages :: uintptr(239) +SYS_rt_tgsigqueueinfo :: uintptr(240) +SYS_perf_event_open :: uintptr(241) +SYS_accept4 :: uintptr(242) +SYS_recvmmsg :: uintptr(243) +SYS_riscv_hwprobe :: uintptr(258) +SYS_wait4 :: uintptr(260) +SYS_prlimit64 :: uintptr(261) +SYS_fanotify_init :: uintptr(262) +SYS_fanotify_mark :: uintptr(263) +SYS_name_to_handle_at :: uintptr(264) +SYS_open_by_handle_at :: uintptr(265) +SYS_clock_adjtime :: uintptr(266) +SYS_syncfs :: uintptr(267) +SYS_setns :: uintptr(268) +SYS_sendmmsg :: uintptr(269) +SYS_process_vm_readv :: uintptr(270) +SYS_process_vm_writev :: uintptr(271) +SYS_kcmp :: uintptr(272) +SYS_finit_module :: uintptr(273) +SYS_sched_setattr :: uintptr(274) +SYS_sched_getattr :: uintptr(275) +SYS_renameat2 :: uintptr(276) +SYS_seccomp :: uintptr(277) +SYS_getrandom :: uintptr(278) +SYS_memfd_create :: uintptr(279) +SYS_bpf :: uintptr(280) +SYS_execveat :: uintptr(281) +SYS_userfaultfd :: uintptr(282) +SYS_membarrier :: uintptr(283) +SYS_mlock2 :: uintptr(284) +SYS_copy_file_range :: uintptr(285) +SYS_preadv2 :: uintptr(286) +SYS_pwritev2 :: uintptr(287) +SYS_pkey_mprotect :: uintptr(288) +SYS_pkey_alloc :: uintptr(289) +SYS_pkey_free :: uintptr(290) +SYS_statx :: uintptr(291) +SYS_io_pgetevents :: uintptr(292) +SYS_rseq :: uintptr(293) +SYS_kexec_file_load :: uintptr(294) +SYS_clock_gettime64 :: uintptr(403) +SYS_clock_settime64 :: uintptr(404) +SYS_clock_adjtime64 :: uintptr(405) +SYS_clock_getres_time64 :: uintptr(406) +SYS_clock_nanosleep_time64 :: uintptr(407) +SYS_timer_gettime64 :: uintptr(408) +SYS_timer_settime64 :: uintptr(409) +SYS_timerfd_gettime64 :: uintptr(410) +SYS_timerfd_settime64 :: uintptr(411) +SYS_utimensat_time64 :: uintptr(412) +SYS_pselect6_time64 :: uintptr(413) +SYS_ppoll_time64 :: uintptr(414) +SYS_io_pgetevents_time64 :: uintptr(416) +SYS_recvmmsg_time64 :: uintptr(417) +SYS_mq_timedsend_time64 :: uintptr(418) +SYS_mq_timedreceive_time64 :: uintptr(419) +SYS_semtimedop_time64 :: uintptr(420) +SYS_rt_sigtimedwait_time64 :: uintptr(421) +SYS_futex_time64 :: uintptr(422) +SYS_sched_rr_get_interval_time64 :: uintptr(423) +SYS_pidfd_send_signal :: uintptr(424) +SYS_io_uring_setup :: uintptr(425) +SYS_io_uring_enter :: uintptr(426) +SYS_io_uring_register :: uintptr(427) +SYS_open_tree :: uintptr(428) +SYS_move_mount :: uintptr(429) +SYS_fsopen :: uintptr(430) +SYS_fsconfig :: uintptr(431) +SYS_fsmount :: uintptr(432) +SYS_fspick :: uintptr(433) +SYS_pidfd_open :: uintptr(434) +SYS_clone3 :: uintptr(435) +SYS_close_range :: uintptr(436) +SYS_openat2 :: uintptr(437) +SYS_pidfd_getfd :: uintptr(438) +SYS_faccessat2 :: uintptr(439) +SYS_process_madvise :: uintptr(440) +SYS_epoll_pwait2 :: uintptr(441) +SYS_mount_setattr :: uintptr(442) +SYS_quotactl_fd :: uintptr(443) +SYS_landlock_create_ruleset :: uintptr(444) +SYS_landlock_add_rule :: uintptr(445) +SYS_landlock_restrict_self :: uintptr(446) +SYS_memfd_secret :: uintptr(447) +SYS_process_mrelease :: uintptr(448) +SYS_futex_waitv :: uintptr(449) +SYS_set_mempolicy_home_node :: uintptr(450) +SYS_cachestat :: uintptr(451) +SYS_fchmodat2 :: uintptr(452) diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 288edf879..0e5b8218b 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -119,7 +119,7 @@ when ODIN_ARCH == .amd64 { ctime: Time_Spec, _: [3]uint, } -} else when ODIN_ARCH == .arm64 { +} else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { _Arch_Stat :: struct { dev: Dev, ino: Inode, @@ -136,7 +136,7 @@ when ODIN_ARCH == .amd64 { atime: Time_Spec, mtime: Time_Spec, ctime: Time_Spec, - _: [3]uint, + _: [2]u32, } } else { _Arch_Stat :: struct { @@ -927,7 +927,7 @@ when ODIN_ARCH == .i386 { nsems: uint, _: [2]uint, } -} else when ODIN_ARCH == .arm64 { +} else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { _Arch_Semid_DS :: struct { perm: IPC_Perm, otime: int, @@ -1167,6 +1167,33 @@ when ODIN_ARCH == .arm32 { xmm_space: [32]uint, padding: [56]uint, } +} else when ODIN_ARCH == .riscv64 { + _Arch_User_Regs :: struct { + pc, ra, sp, gp, tp, + t0, t1, t2, + s0, s1, + a0, a1, a2, a3, a4, a5, a6, a7, + s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, + t3, t4, t5, t6: uint, + } + _Arch_User_FP_Regs :: struct #raw_union { + f_ext: struct { + f: [32]u32, + fcsr: u32, + }, + d_ext: struct { + f: [32]u64, + fcsr: u32, + }, + q_ext: struct { + using _: struct #align(16) { + f: [64]u64, + }, + fcsr: u32, + reserved: [3]u32, + }, + } + _Arch_User_FPX_Regs :: struct {} } /* @@ -1308,3 +1335,20 @@ EPoll_Event :: struct #packed { Flags for execveat(2) syscall. */ Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32] + +RISCV_HWProbe_Flags :: bit_set[RISCV_HWProbe_Flags_Bits; u32] +RISCV_HWProbe_CPU_Perf_0 :: bit_set[RISCV_HWProbe_Misaligned_Scalar_Perf; u64] +RISCV_HWProbe_Base_Behavior :: bit_set[RISCV_HWProbe_Base_Behavior_Bits; u64] +RISCV_HWProbe_IMA_Ext_0 :: bit_set[RISCV_HWProbe_IMA_Ext_0_Bits; u64] + +RISCV_HWProbe :: struct { + // set to `.UNSUPPORTED` by the kernel if that is the case. + key: RISCV_HWProbe_Key, + value: struct #raw_union { + base_behavior: RISCV_HWProbe_Base_Behavior, + ima_ext_0: RISCV_HWProbe_IMA_Ext_0, + cpu_perf_0: RISCV_HWProbe_CPU_Perf_0, + misaligned_scalar_perf: RISCV_HWProbe_Misaligned_Scalar_Perf, + raw: u64, + }, +} diff --git a/core/sys/llvm/bit_manipulation.odin b/core/sys/llvm/bit_manipulation.odin index 39464773d..2e237dd32 100644 --- a/core/sys/llvm/bit_manipulation.odin +++ b/core/sys/llvm/bit_manipulation.odin @@ -1,4 +1,5 @@ // Bit Manipulation Intrinsics + package sys_llvm /* diff --git a/core/sys/llvm/code_generator.odin b/core/sys/llvm/code_generator.odin index 7d41ed67b..6422976c5 100644 --- a/core/sys/llvm/code_generator.odin +++ b/core/sys/llvm/code_generator.odin @@ -1,4 +1,5 @@ // Code Generator Intrinsics + package sys_llvm @(default_calling_convention="none") diff --git a/core/sys/llvm/standard_c_library.odin b/core/sys/llvm/standard_c_library.odin index 108d1268e..1818e8462 100644 --- a/core/sys/llvm/standard_c_library.odin +++ b/core/sys/llvm/standard_c_library.odin @@ -1,4 +1,5 @@ // Standard C Library Intrinsics + package sys_llvm @(default_calling_convention="none") diff --git a/core/sys/orca/macros.odin b/core/sys/orca/macros.odin new file mode 100644 index 000000000..d6a1a0f82 --- /dev/null +++ b/core/sys/orca/macros.odin @@ -0,0 +1,252 @@ +// File contains implementations of the Orca API that are defined as macros in Orca. + +package orca + +//////////////////////////////////////////////////////////////////////////////// +// Helpers for logging, asserting and aborting. +//////////////////////////////////////////////////////////////////////////////// + +log_error :: proc "contextless" (msg: cstring, loc := #caller_location) { + log_ext( + .ERROR, + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +log_warning :: proc "contextless" (msg: cstring, loc := #caller_location) { + log_ext( + .WARNING, + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +log_info :: proc "contextless" (msg: cstring, loc := #caller_location) { + log_ext( + .INFO, + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +abort :: proc "contextless" (msg: cstring, loc := #caller_location) { + abort_ext( + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +//////////////////////////////////////////////////////////////////////////////// +// Types and helpers for doubly-linked lists. +//////////////////////////////////////////////////////////////////////////////// + +// Get the entry for a given list element. +list_entry :: proc "contextless" (elt: ^list_elt, $T: typeid, $member: string) -> ^T { + return container_of(elt, T, member) +} + +// Get the next entry in a list. +list_next_entry :: proc "contextless" (list: ^list, elt: ^list_elt, $T: typeid, $member: string) -> ^T { + if elt.next != list.last { + return list_entry(elt.next, T, member) + } + + return nil +} + +// Get the previous entry in a list. +list_prev_entry :: proc "contextless" (list: ^list, elt: ^list_elt, $T: typeid, $member: string) -> ^T { + if elt.prev != list.last { + return list_entry(elt.prev, T, member) + } + + return nil +} + +// Same as `list_entry` but `elt` might be `nil`. +list_checked_entry :: proc "contextless" (elt: ^list_elt, $T: typeid, $member: string) -> ^T { + if elt != nil { + return list_entry(elt, T, member) + } + + return nil +} + +list_first_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + return list_checked_entry(list.first, T, member) +} + +list_last_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + return list_checked_entry(list.last, T, member) +} + +// Example: +// +// _elt: ^list_elt +// for elt in oc.list_for(list, &_elt, int, "elt") { +// } +list_for :: proc "contextless" (list: ^list, elt: ^^list_elt, $T: typeid, $member: string) -> (^T, bool) { + if elt == nil { + assert_fail(#file, #procedure, #line, "elt != nil", "misuse of `list_for`, expected `elt` to not be nil") + } + + if elt^ == nil { + elt^ = list.first + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil + } + + elt^ = elt^.next + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil +} + +list_iter :: list_for + +list_for_reverse :: proc "contextless" (list: ^list, elt: ^^list_elt, $T: typeid, $member: string) -> (^T, bool) { + if elt^ == nil { + elt^ = list.last + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil + } + + elt^ = elt^.prev + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil +} + +list_iter_reverse :: list_for_reverse + +list_pop_front_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + if list_empty(list^) { + return nil + } + + return list_entry(list_pop_front(list), T, member) +} + +list_pop_back_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + if list_empty(list^) { + return nil + } + + return list_entry(list_pop_back(list), T, member) +} + +//////////////////////////////////////////////////////////////////////////////// +// Base allocator and memory arenas. +//////////////////////////////////////////////////////////////////////////////// + +arena_push_type :: proc "contextless" (arena: ^arena, $T: typeid) -> ^T { + return (^T)(arena_push_aligned(arena, size_of(T), align_of(T))) +} + +arena_push_array :: proc "contextless" (arena: ^arena, $T: typeid, count: u64) -> []T { + return ([^]T)(arena_push_aligned(arena, size_of(T) * count, align_of(T)))[:count] +} + +scratch_end :: arena_scope_end + +//////////////////////////////////////////////////////////////////////////////// +// String slices and string lists. +//////////////////////////////////////////////////////////////////////////////// + +str8_list_first :: proc "contextless" (sl: ^str8_list) -> str8 { + if list_empty(sl.list) { + return "" + } + + return list_first_entry(&sl.list, str8_elt, "listElt").string +} + +str8_list_last :: proc "contextless" (sl: ^str8_list) -> str8 { + if list_empty(sl.list) { + return "" + } + + return list_last_entry(&sl.list, str8_elt, "listElt").string +} + +str8_list_for :: proc "contextless" (list: ^str8_list, elt: ^^list_elt) -> (^str8_elt, bool) { + return list_for(&list.list, elt, str8_elt, "listElt") +} + +str8_list_iter :: str8_list_for + +str8_list_empty :: proc "contextless" (list: str8_list) -> bool { + return list_empty(list.list) +} + +str16_list_first :: proc "contextless" (sl: ^str16_list) -> str16 { + if list_empty(sl.list) { + return {} + } + + return list_first_entry(&sl.list, str16_elt, "listElt").string +} + +str16_list_last :: proc "contextless" (sl: ^str16_list) -> str16 { + if list_empty(sl.list) { + return {} + } + + return list_last_entry(&sl.list, str16_elt, "listElt").string +} + +str16_list_for :: proc "contextless" (list: ^str16_list, elt: ^^list_elt) -> (^str16_elt, bool) { + return list_for(&list.list, elt, str16_elt, "listElt") +} + +str32_list_first :: proc "contextless" (sl: ^str32_list) -> str32 { + if list_empty(sl.list) { + return {} + } + + return list_first_entry(&sl.list, str32_elt, "listElt").string +} + +str32_list_last :: proc "contextless" (sl: ^str32_list) -> str32 { + if list_empty(sl.list) { + return {} + } + + return list_last_entry(&sl.list, str32_elt, "listElt").string +} + +str32_list_for :: proc "contextless" (list: ^str32_list, elt: ^^list_elt) -> (^str32_elt, bool) { + return list_for(&list.list, elt, str32_elt, "listElt") +} + +@(deferred_none=ui_box_end) +ui_container :: proc "contextless" (name: string, flags: ui_flags = {}) -> ^ui_box { + return ui_box_begin_str8(name, flags) +} + +@(deferred_none=ui_end_frame) +ui_frame :: proc "contextless" (frame_size: [2]f32, style: ui_style, mask: ui_style_mask) { + ui_begin_frame(frame_size, style, mask) +} + +@(deferred_none=ui_panel_end) +ui_panel :: proc "contextless" (name: cstring, flags: ui_flags) { + ui_panel_begin(name, flags) +} + +@(deferred_none=ui_menu_end) +ui_menu :: proc "contextless" (name: cstring) { + ui_menu_begin(name) +} + +@(deferred_none=ui_menu_bar_end) +ui_menu_bar :: proc "contextless" (name: cstring) { + ui_menu_bar_begin(name) +} diff --git a/core/sys/orca/odin.odin b/core/sys/orca/odin.odin new file mode 100644 index 000000000..5c3e3e4d9 --- /dev/null +++ b/core/sys/orca/odin.odin @@ -0,0 +1,22 @@ +// File contains Odin specific helpers. + +package orca + +import "base:runtime" + +create_odin_logger :: proc(lowest := runtime.Logger_Level.Debug, ident := "") -> runtime.Logger { + return runtime.Logger{odin_logger_proc, nil, lowest, {}} +} + +odin_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + cbuf := make([]byte, len(text)+1, context.temp_allocator) + copy(cbuf, text) + ctext := cstring(raw_data(cbuf)) + + switch level { + case .Debug, .Info: log_info(ctext, location) + case .Warning: log_warning(ctext, location) + case: fallthrough + case .Error, .Fatal: log_error(ctext, location) + } +} diff --git a/core/sys/orca/orca.odin b/core/sys/orca/orca.odin new file mode 100644 index 000000000..d1e7dbf66 --- /dev/null +++ b/core/sys/orca/orca.odin @@ -0,0 +1,2178 @@ +package orca + +import "core:c" + +char :: c.char + +// currently missing in the api.json +window :: distinct u64 + +// currently missing in the api.json +pool :: struct { + arena: arena, + freeList: list, + blockSize: u64, +} + +@(link_prefix="OC_") +foreign { + UI_DARK_THEME: ui_theme + UI_LIGHT_THEME: ui_theme + + UI_DARK_PALETTE: ui_palette + UI_LIGHT_PALETTE: ui_palette +} + + +SYS_MAX_ERROR :: 1024 + +sys_err_def :: struct { + msg: [SYS_MAX_ERROR]u8 `fmt:"s,0"`, + code: i32, +} + +@(link_prefix="oc_") +foreign { + sys_error: sys_err_def +} +UNICODE_BASIC_LATIN :: unicode_range { 0x0000, 127 } +UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT :: unicode_range { 0x0080, 127 } +UNICODE_LATIN_EXTENDED_A :: unicode_range { 0x0100, 127 } +UNICODE_LATIN_EXTENDED_B :: unicode_range { 0x0180, 207 } +UNICODE_IPA_EXTENSIONS :: unicode_range { 0x0250, 95 } +UNICODE_SPACING_MODIFIER_LETTERS :: unicode_range { 0x02b0, 79 } +UNICODE_COMBINING_DIACRITICAL_MARKS :: unicode_range { 0x0300, 111 } +UNICODE_GREEK_COPTIC :: unicode_range { 0x0370, 143 } +UNICODE_CYRILLIC :: unicode_range { 0x0400, 255 } +UNICODE_CYRILLIC_SUPPLEMENT :: unicode_range { 0x0500, 47 } +UNICODE_ARMENIAN :: unicode_range { 0x0530, 95 } +UNICODE_HEBREW :: unicode_range { 0x0590, 111 } +UNICODE_ARABIC :: unicode_range { 0x0600, 255 } +UNICODE_SYRIAC :: unicode_range { 0x0700, 79 } +UNICODE_THAANA :: unicode_range { 0x0780, 63 } +UNICODE_DEVANAGARI :: unicode_range { 0x0900, 127 } +UNICODE_BENGALI_ASSAMESE :: unicode_range { 0x0980, 127 } +UNICODE_GURMUKHI :: unicode_range { 0x0a00, 127 } +UNICODE_GUJARATI :: unicode_range { 0x0a80, 127 } +UNICODE_ORIYA :: unicode_range { 0x0b00, 127 } +UNICODE_TAMIL :: unicode_range { 0x0b80, 127 } +UNICODE_TELUGU :: unicode_range { 0x0c00, 127 } +UNICODE_KANNADA :: unicode_range { 0x0c80, 127 } +UNICODE_MALAYALAM :: unicode_range { 0x0d00, 255 } +UNICODE_SINHALA :: unicode_range { 0x0d80, 127 } +UNICODE_THAI :: unicode_range { 0x0e00, 127 } +UNICODE_LAO :: unicode_range { 0x0e80, 127 } +UNICODE_TIBETAN :: unicode_range { 0x0f00, 255 } +UNICODE_MYANMAR :: unicode_range { 0x1000, 159 } +UNICODE_GEORGIAN :: unicode_range { 0x10a0, 95 } +UNICODE_HANGUL_JAMO :: unicode_range { 0x1100, 255 } +UNICODE_ETHIOPIC :: unicode_range { 0x1200, 383 } +UNICODE_CHEROKEE :: unicode_range { 0x13a0, 95 } +UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS :: unicode_range { 0x1400, 639 } +UNICODE_OGHAM :: unicode_range { 0x1680, 31 } +UNICODE_RUNIC :: unicode_range { 0x16a0, 95 } +UNICODE_TAGALOG :: unicode_range { 0x1700, 31 } +UNICODE_HANUNOO :: unicode_range { 0x1720, 31 } +UNICODE_BUHID :: unicode_range { 0x1740, 31 } +UNICODE_TAGBANWA :: unicode_range { 0x1760, 31 } +UNICODE_KHMER :: unicode_range { 0x1780, 127 } +UNICODE_MONGOLIAN :: unicode_range { 0x1800, 175 } +UNICODE_LIMBU :: unicode_range { 0x1900, 79 } +UNICODE_TAI_LE :: unicode_range { 0x1950, 47 } +UNICODE_KHMER_SYMBOLS :: unicode_range { 0x19e0, 31 } +UNICODE_PHONETIC_EXTENSIONS :: unicode_range { 0x1d00, 127 } +UNICODE_LATIN_EXTENDED_ADDITIONAL :: unicode_range { 0x1e00, 255 } +UNICODE_GREEK_EXTENDED :: unicode_range { 0x1f00, 255 } +UNICODE_GENERAL_PUNCTUATION :: unicode_range { 0x2000, 111 } +UNICODE_SUPERSCRIPTS_AND_SUBSCRIPTS :: unicode_range { 0x2070, 47 } +UNICODE_CURRENCY_SYMBOLS :: unicode_range { 0x20a0, 47 } +UNICODE_COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS :: unicode_range { 0x20d0, 47 } +UNICODE_LETTERLIKE_SYMBOLS :: unicode_range { 0x2100, 79 } +UNICODE_NUMBER_FORMS :: unicode_range { 0x2150, 63 } +UNICODE_ARROWS :: unicode_range { 0x2190, 111 } +UNICODE_MATHEMATICAL_OPERATORS :: unicode_range { 0x2200, 255 } +UNICODE_MISCELLANEOUS_TECHNICAL :: unicode_range { 0x2300, 255 } +UNICODE_CONTROL_PICTURES :: unicode_range { 0x2400, 63 } +UNICODE_OPTICAL_CHARACTER_RECOGNITION :: unicode_range { 0x2440, 31 } +UNICODE_ENCLOSED_ALPHANUMERICS :: unicode_range { 0x2460, 159 } +UNICODE_BOX_DRAWING :: unicode_range { 0x2500, 127 } +UNICODE_BLOCK_ELEMENTS :: unicode_range { 0x2580, 31 } +UNICODE_GEOMETRIC_SHAPES :: unicode_range { 0x25a0, 95 } +UNICODE_MISCELLANEOUS_SYMBOLS :: unicode_range { 0x2600, 255 } +UNICODE_DINGBATS :: unicode_range { 0x2700, 191 } +UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A :: unicode_range { 0x27c0, 47 } +UNICODE_SUPPLEMENTAL_ARROWS_A :: unicode_range { 0x27f0, 15 } +UNICODE_BRAILLE_PATTERNS :: unicode_range { 0x2800, 255 } +UNICODE_SUPPLEMENTAL_ARROWS_B :: unicode_range { 0x2900, 127 } +UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B :: unicode_range { 0x2980, 127 } +UNICODE_SUPPLEMENTAL_MATHEMATICAL_OPERATORS :: unicode_range { 0x2a00, 255 } +UNICODE_MISCELLANEOUS_SYMBOLS_AND_ARROWS :: unicode_range { 0x2b00, 255 } +UNICODE_CJK_RADICALS_SUPPLEMENT :: unicode_range { 0x2e80, 127 } +UNICODE_KANGXI_RADICALS :: unicode_range { 0x2f00, 223 } +UNICODE_IDEOGRAPHIC_DESCRIPTION_CHARACTERS :: unicode_range { 0x2ff0, 15 } +UNICODE_CJK_SYMBOLS_AND_PUNCTUATION :: unicode_range { 0x3000, 63 } +UNICODE_HIRAGANA :: unicode_range { 0x3040, 95 } +UNICODE_KATAKANA :: unicode_range { 0x30a0, 95 } +UNICODE_BOPOMOFO :: unicode_range { 0x3100, 47 } +UNICODE_HANGUL_COMPATIBILITY_JAMO :: unicode_range { 0x3130, 95 } +UNICODE_KANBUN_KUNTEN :: unicode_range { 0x3190, 15 } +UNICODE_BOPOMOFO_EXTENDED :: unicode_range { 0x31a0, 31 } +UNICODE_KATAKANA_PHONETIC_EXTENSIONS :: unicode_range { 0x31f0, 15 } +UNICODE_ENCLOSED_CJK_LETTERS_AND_MONTHS :: unicode_range { 0x3200, 255 } +UNICODE_CJK_COMPATIBILITY :: unicode_range { 0x3300, 255 } +UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A :: unicode_range { 0x3400, 6591 } +UNICODE_YIJING_HEXAGRAM_SYMBOLS :: unicode_range { 0x4dc0, 63 } +UNICODE_CJK_UNIFIED_IDEOGRAPHS :: unicode_range { 0x4e00, 20911 } +UNICODE_YI_SYLLABLES :: unicode_range { 0xa000, 1167 } +UNICODE_YI_RADICALS :: unicode_range { 0xa490, 63 } +UNICODE_HANGUL_SYLLABLES :: unicode_range { 0xac00, 11183 } +UNICODE_HIGH_SURROGATE_AREA :: unicode_range { 0xd800, 1023 } +UNICODE_LOW_SURROGATE_AREA :: unicode_range { 0xdc00, 1023 } +UNICODE_PRIVATE_USE_AREA :: unicode_range { 0xe000, 6399 } +UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS :: unicode_range { 0xf900, 511 } +UNICODE_ALPHABETIC_PRESENTATION_FORMS :: unicode_range { 0xfb00, 79 } +UNICODE_ARABIC_PRESENTATION_FORMS_A :: unicode_range { 0xfb50, 687 } +UNICODE_VARIATION_SELECTORS :: unicode_range { 0xfe00, 15 } +UNICODE_COMBINING_HALF_MARKS :: unicode_range { 0xfe20, 15 } +UNICODE_CJK_COMPATIBILITY_FORMS :: unicode_range { 0xfe30, 31 } +UNICODE_SMALL_FORM_VARIANTS :: unicode_range { 0xfe50, 31 } +UNICODE_ARABIC_PRESENTATION_FORMS_B :: unicode_range { 0xfe70, 143 } +UNICODE_HALFWIDTH_AND_FULLWIDTH_FORMS :: unicode_range { 0xff00, 239 } +UNICODE_SPECIALS :: unicode_range { 0xfff0, 15 } +UNICODE_LINEAR_B_SYLLABARY :: unicode_range { 0x10000, 127 } +UNICODE_LINEAR_B_IDEOGRAMS :: unicode_range { 0x10080, 127 } +UNICODE_AEGEAN_NUMBERS :: unicode_range { 0x10100, 63 } +UNICODE_OLD_ITALIC :: unicode_range { 0x10300, 47 } +UNICODE_GOTHIC :: unicode_range { 0x10330, 31 } +UNICODE_UGARITIC :: unicode_range { 0x10380, 31 } +UNICODE_DESERET :: unicode_range { 0x10400, 79 } +UNICODE_SHAVIAN :: unicode_range { 0x10450, 47 } +UNICODE_OSMANYA :: unicode_range { 0x10480, 47 } +UNICODE_CYPRIOT_SYLLABARY :: unicode_range { 0x10800, 63 } +UNICODE_BYZANTINE_MUSICAL_SYMBOLS :: unicode_range { 0x1d000, 255 } +UNICODE_MUSICAL_SYMBOLS :: unicode_range { 0x1d100, 255 } +UNICODE_TAI_XUAN_JING_SYMBOLS :: unicode_range { 0x1d300, 95 } +UNICODE_MATHEMATICAL_ALPHANUMERIC_SYMBOLS :: unicode_range { 0x1d400, 1023 } +UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B :: unicode_range { 0x20000, 42719 } +UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT :: unicode_range { 0x2f800, 543 } +UNICODE_TAGS :: unicode_range { 0xe0000, 127 } +UNICODE_VARIATION_SELECTORS_SUPPLEMENT :: unicode_range { 0xe0100, 239 } +UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A :: unicode_range { 0xf0000, 65533 } +UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B :: unicode_range { 0x100000, 65533 } + +clock_kind :: enum c.int { + MONOTONIC, + UPTIME, + DATE, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + clock_time :: proc(clock: clock_kind) -> f64 --- +} + +file_write_slice :: proc(file: file, slice: []char) -> u64 { + return file_write(file, u64(len(slice)), raw_data(slice)) +} + +file_read_slice :: proc(file: file, slice: []char) -> u64 { + return file_read(file, u64(len(slice)), raw_data(slice)) +} + +style_enum :: enum { + SIZE_WIDTH = 1, + SIZE_HEIGHT, + + LAYOUT_AXIS, + LAYOUT_ALIGN_X, + LAYOUT_ALIGN_Y, + LAYOUT_SPACING, + LAYOUT_MARGIN_X, + LAYOUT_MARGIN_Y, + + FLOAT_X, + FLOAT_Y, + + COLOR, + BG_COLOR, + BORDER_COLOR, + BORDER_SIZE, + ROUNDNESS, + + FONT, + FONT_SIZE, + + ANIMATION_TIME, + ANIMATION_MASK, +} + +ui_style_mask :: bit_set[style_enum; u64] + +// Masks like the C version that can be used as common combinations +SIZE :: ui_style_mask { .SIZE_WIDTH, .SIZE_HEIGHT } +LAYOUT_MARGINS :: ui_style_mask { .LAYOUT_MARGIN_X, .LAYOUT_MARGIN_Y } +LAYOUT :: ui_style_mask { .LAYOUT_AXIS, .LAYOUT_ALIGN_X, .LAYOUT_ALIGN_Y, .LAYOUT_SPACING, .LAYOUT_MARGIN_X, .LAYOUT_MARGIN_Y } +FLOAT :: ui_style_mask { .FLOAT_X, .FLOAT_Y } +MASK_INHERITED :: ui_style_mask { .COLOR, .FONT, .FONT_SIZE, .ANIMATION_TIME, .ANIMATION_MASK } + +//////////////////////////////////////////////////////////////////////////////// +// Utility data structures and helpers used throughout the Orca API. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Types and helpers for vectors and matrices. +//////////////////////////////////////////////////////////////////////////////// + +// A 2D vector type. +vec2 :: [2]f32 + +// A 3D vector type. +vec3 :: [3]f32 + +// A 2D integer vector type. +vec2i :: [2]i32 + +// A 4D vector type. +vec4 :: [4]f32 + +// A 2-by-3 matrix. +mat2x3 :: [6]f32 + +// An axis-aligned rectangle. +rect :: struct { x, y, w, h: f32 } + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Check if two 2D vectors are equal. + vec2_equal :: proc(v0: vec2, v1: vec2) -> bool --- + // Multiply a 2D vector by a scalar. + vec2_mul :: proc(f: f32, v: vec2) -> vec2 --- + // Add two 2D vectors + vec2_add :: proc(v0: vec2, v1: vec2) -> vec2 --- + // Transforms a vector by an affine transformation represented as a 2x3 matrix. + mat2x3_mul :: proc(m: mat2x3, p: vec2) -> vec2 --- + // Multiply two affine transformations represented as 2x3 matrices. Both matrices are treated as 3x3 matrices with an implicit `(0, 0, 1)` bottom row + mat2x3_mul_m :: proc(lhs: mat2x3, rhs: mat2x3) -> mat2x3 --- + // Invert an affine transform represented as a 2x3 matrix. + mat2x3_inv :: proc(x: mat2x3) -> mat2x3 --- + // Return a 2x3 matrix representing a rotation. + mat2x3_rotate :: proc(radians: f32) -> mat2x3 --- + // Return a 2x3 matrix representing a translation. + mat2x3_translate :: proc(x: f32, y: f32) -> mat2x3 --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Helpers for logging, asserting and aborting. +//////////////////////////////////////////////////////////////////////////////// + +// Constants allowing to specify the level of logging verbosity. +log_level :: enum u32 { + // Only errors are logged. + ERROR = 0, + // Only warnings and errors are logged. + WARNING = 1, + // All messages are logged. + INFO = 2, + COUNT = 3, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + /* + Abort the application, showing an error message. + + This function should not be called directly by user code, which should use the `OC_ABORT` macro instead, as the macro takes care of filling in the source location parameters of the function. + */ + abort_ext :: proc(file: cstring, function: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) -> ! --- + /* + Tigger a failed assertion. This aborts the application, showing the failed assertion and an error message. + + This function should not be called directly by user code, which should use the `OC_ASSERT` macro instead. The macro checks the assert condition and calls the function if it is false. It also takes care of filling in the source location parameters of the function. + */ + assert_fail :: proc(file: cstring, function: cstring, line: i32, src: cstring, fmt: cstring, #c_vararg args: ..any) -> ! --- + // Set the logging verbosity. + log_set_level :: proc(level: log_level) --- + /* + Log a message to the console. + + This function should not be called directly by user code, which should use the `oc_log_XXX` family of macros instead. The macros take care of filling in the message level and source location parameters of the function. + */ + log_ext :: proc(level: log_level, function: cstring, file: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Types and helpers for doubly-linked lists. +//////////////////////////////////////////////////////////////////////////////// + +// An element of an intrusive doubly-linked list. +list_elt :: struct { + // Points to the previous element in the list. + prev: ^list_elt, + // Points to the next element in the list. + next: ^list_elt, +} + +// A doubly-linked list. +list :: struct { + // Points to the first element in the list. + first: ^list_elt, + // Points to the last element in the list. + last: ^list_elt, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Check if a list is empty. + list_empty :: proc(list: list) -> bool --- + // Zero-initializes a linked list. + list_init :: proc(list: ^list) --- + // Insert an element in a list after a given element. + list_insert :: proc(list: ^list, afterElt: ^list_elt, elt: ^list_elt) --- + // Insert an element in a list before a given element. + list_insert_before :: proc(list: ^list, beforeElt: ^list_elt, elt: ^list_elt) --- + // Remove an element from a list. + list_remove :: proc(list: ^list, elt: ^list_elt) --- + // Add an element at the end of a list. + list_push_back :: proc(list: ^list, elt: ^list_elt) --- + // Remove the last element from a list. + list_pop_back :: proc(list: ^list) -> ^list_elt --- + // Add an element at the beginning of a list. + list_push_front :: proc(list: ^list, elt: ^list_elt) --- + // Remove the first element from a list. + list_pop_front :: proc(list: ^list) -> ^list_elt --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Base allocator and memory arenas. +//////////////////////////////////////////////////////////////////////////////// + +// The prototype of a procedure to reserve memory from the system. +mem_reserve_proc :: proc "c" (_context: ^base_allocator, size: u64) -> rawptr + +// The prototype of a procedure to modify a memory reservation. +mem_modify_proc :: proc "c" (_context: ^base_allocator, ptr: rawptr, size: u64) + +// A structure that defines how to allocate memory from the system. +base_allocator :: struct { + // A procedure to reserve memory from the system. + reserve: mem_reserve_proc, + // A procedure to commit memory from the system. + commit: mem_modify_proc, + // A procedure to decommit memory from the system. + decommit: mem_modify_proc, + // A procedure to release memory previously reserved from the system. + release: mem_modify_proc, +} + +// A contiguous chunk of memory managed by a memory arena. +arena_chunk :: struct { + listElt: list_elt, + ptr: cstring, + offset: u64, + committed: u64, + cap: u64, +} + +// A memory arena, allowing to allocate memory in a linear or stack-like fashion. +arena :: struct { + // An allocator providing memory pages from the system + base: ^base_allocator, + // A list of `oc_arena_chunk` chunks. + chunks: list, + // The chunk new memory allocations are pulled from. + currentChunk: ^arena_chunk, +} + +// This struct provides a way to store the current offset in a given arena, in order to reset the arena to that offset later. This allows using arenas in a stack-like fashion, e.g. to create temporary "scratch" allocations +arena_scope :: struct { + // The arena which offset is stored. + arena: ^arena, + // The arena chunk to which the offset belongs. + chunk: ^arena_chunk, + // The offset to rewind the arena to. + offset: u64, +} + +// Options for arena creation. +arena_options :: struct { + // The base allocator to use with this arena + base: ^base_allocator, + // The amount of memory to reserve up-front when creating the arena. + reserve: u64, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Initialize a memory arena. + arena_init :: proc(arena: ^arena) --- + // Initialize a memory arena with additional options. + arena_init_with_options :: proc(arena: ^arena, options: ^arena_options) --- + // Release all resources allocated to a memory arena. + arena_cleanup :: proc(arena: ^arena) --- + // Allocate a block of memory from an arena. + arena_push :: proc(arena: ^arena, size: u64) -> rawptr --- + // Allocate an aligned block of memory from an arena. + arena_push_aligned :: proc(arena: ^arena, size: u64, alignment: u32) -> rawptr --- + // Reset an arena. All memory that was previously allocated from this arena is released to the arena, and can be reallocated by later calls to `oc_arena_push` and similar functions. No memory is actually released _to the system_. + arena_clear :: proc(arena: ^arena) --- + // Begin a memory scope. This creates an `oc_arena_scope` object that stores the current offset of the arena. The arena can later be reset to that offset by calling `oc_arena_scope_end`, releasing all memory that was allocated within the scope to the arena. + arena_scope_begin :: proc(arena: ^arena) -> arena_scope --- + // End a memory scope. This resets an arena to the offset it had when the scope was created. All memory allocated within the scope is released back to the arena. + arena_scope_end :: proc(scope: arena_scope) --- + /* + Begin a scratch scope. This creates a memory scope on a per-thread, global "scratch" arena. This allows easily creating temporary memory for scratch computations or intermediate results, in a stack-like fashion. + + If you must return results in an arena passed by the caller, and you also use a scratch arena to do intermediate computations, beware that the results arena could itself be a scatch arena. In this case, you have to be careful not to intermingle your scratch computations with the final result, or clear your result entirely. You can either: + + - Allocate memory for the result upfront and call `oc_scratch_begin` afterwards, if possible. + - Use `oc_scratch_begin_next()` and pass it the result arena, to get a scratch arena that does not conflict with it. + */ + scratch_begin :: proc() -> arena_scope --- + // Begin a scratch scope that does not conflict with a given arena. See `oc_scratch_begin()` for more details about when to use this function. + scratch_begin_next :: proc(used: ^arena) -> arena_scope --- +} + +//////////////////////////////////////////////////////////////////////////////// +// String slices and string lists. +//////////////////////////////////////////////////////////////////////////////// + +// A type representing a string of bytes. +str8 :: string + +// A type representing an element of a string list. +str8_elt :: struct { + // The string element is linked into its parent string list through this field. + listElt: list_elt, + // The string for this element. + string: str8, +} + +// A type representing a string list. +str8_list :: struct { + // A linked-list of `oc_str8_elt`. + list: list, + // The number of elements in `list`. + eltCount: u64, + // The total length of the string list, which is the sum of the lengths over all elements. + len: u64, +} + +// A type describing a string of 16-bits characters (typically used for UTF-16). +str16 :: distinct []u16 + +// A type representing an element of an `oc_str16` list. +str16_elt :: struct { + // The string element is linked into its parent string list through this field. + listElt: list_elt, + // The string for this element. + string: str16, +} + +str16_list :: struct { + // A linked-list of `oc_str16_elt`. + list: list, + // The number of elements in `list`. + eltCount: u64, + // The total length of the string list, which is the sum of the lengths over all elements. + len: u64, +} + +// A type describing a string of 32-bits characters (typically used for UTF-32 codepoints). +str32 :: distinct []rune + +// A type representing an element of an `oc_str32` list. +str32_elt :: struct { + // The string element is linked into its parent string list through this field. + listElt: list_elt, + // The string for this element. + string: str32, +} + +str32_list :: struct { + // A linked-list of `oc_str32_elt`. + list: list, + // The number of elements in `list`. + eltCount: u64, + // The total length of the string list, which is the sum of the lengths over all elements. + len: u64, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Make a string from a bytes buffer and a length. + str8_from_buffer :: proc(len: u64, buffer: [^]char) -> str8 --- + // Make a string from a slice of another string. The resulting string designates some subsequence of the input string. + str8_slice :: proc(s: str8, start: u64, end: u64) -> str8 --- + // Pushes a copy of a buffer to an arena, and makes a string refering to that copy. + str8_push_buffer :: proc(arena: ^arena, len: u64, buffer: [^]char) -> str8 --- + // Pushes a copy of a C null-terminated string to an arena, and makes a string referring to that copy. + str8_push_cstring :: proc(arena: ^arena, str: cstring) -> str8 --- + // Copy the contents of a string on an arena and make a new string referring to the copied bytes. + str8_push_copy :: proc(arena: ^arena, s: str8) -> str8 --- + // Make a copy of a string slice. This function copies a subsequence of the input string onto an arena, and returns a new string referring to the copied content. + str8_push_slice :: proc(arena: ^arena, s: str8, start: u64, end: u64) -> str8 --- + // Lexicographically compare the contents of two strings. + str8_cmp :: proc(s1: str8, s2: str8) -> i32 --- + // Create a null-terminated C-string from an `oc_str8` string. + str8_to_cstring :: proc(arena: ^arena, string: str8) -> cstring --- + // Push a string element to the back of a string list. This creates a `oc_str8_elt` element referring to the contents of the input string, and links that element at the end of the string list. + str8_list_push :: proc(arena: ^arena, list: ^str8_list, str: str8) --- + // Build a string from a null-terminated format string an variadic arguments, and append it to a string list. + str8_list_pushf :: proc(arena: ^arena, list: ^str8_list, format: cstring, #c_vararg args: ..any) --- + // Build a string by combining the elements of a string list with a prefix, a suffix, and separators. + str8_list_collate :: proc(arena: ^arena, list: str8_list, prefix: str8, separator: str8, suffix: str8) -> str8 --- + // Build a string by joining the elements of a string list. + str8_list_join :: proc(arena: ^arena, list: str8_list) -> str8 --- + /* + Split a list into a string list according to separators. + + No string copies are made. The elements of the resulting string list refer to subsequences of the input string. + */ + str8_split :: proc(arena: ^arena, str: str8, separators: str8_list) -> str8_list --- + // Make an `oc_str16` string from a buffer of 16-bit characters. + str16_from_buffer :: proc(len: u64, buffer: [^]u16) -> str16 --- + // Make an `oc_str16` string from a slice of another `oc_str16` string. + str16_slice :: proc(s: str16, start: u64, end: u64) -> str16 --- + // Copy the content of a 16-bit character buffer on an arena and make a new `oc_str16` referencing the copied contents. + str16_push_buffer :: proc(arena: ^arena, len: u64, buffer: [^]u16) -> str16 --- + // Copy the contents of an `oc_str16` string and make a new string referencing the copied contents. + str16_push_copy :: proc(arena: ^arena, s: str16) -> str16 --- + // Copy a slice of an `oc_str16` string an make a new string referencing the copies contents. + str16_push_slice :: proc(arena: ^arena, s: str16, start: u64, end: u64) -> str16 --- + // Push a string element to the back of a string list. This creates a `oc_str16_elt` element referring to the contents of the input string, and links that element at the end of the string list. + str16_list_push :: proc(arena: ^arena, list: ^str16_list, str: str16) --- + // Build a string by joining the elements of a string list. + str16_list_join :: proc(arena: ^arena, list: str16_list) -> str16 --- + /* + Split a list into a string list according to separators. + + No string copies are made. The elements of the resulting string list refer to subsequences of the input string. + */ + str16_split :: proc(arena: ^arena, str: str16, separators: str16_list) -> str16_list --- + // Make an `oc_str32` string from a buffer of 32-bit characters. + str32_from_buffer :: proc(len: u64, buffer: [^]u32) -> str32 --- + // Make an `oc_str32` string from a slice of another `oc_str32` string. + str32_slice :: proc(s: str32, start: u64, end: u64) -> str32 --- + // Copy the content of a 32-bit character buffer on an arena and make a new `oc_str32` referencing the copied contents. + str32_push_buffer :: proc(arena: ^arena, len: u64, buffer: [^]u32) -> str32 --- + // Copy the contents of an `oc_str32` string and make a new string referencing the copied contents. + str32_push_copy :: proc(arena: ^arena, s: str32) -> str32 --- + // Copy a slice of an `oc_str32` string an make a new string referencing the copies contents. + str32_push_slice :: proc(arena: ^arena, s: str32, start: u64, end: u64) -> str32 --- + // Push a string element to the back of a string list. This creates a `oc_str32_elt` element referring to the contents of the input string, and links that element at the end of the string list. + str32_list_push :: proc(arena: ^arena, list: ^str32_list, str: str32) --- + // Build a string by joining the elements of a string list. + str32_list_join :: proc(arena: ^arena, list: str32_list) -> str32 --- + /* + Split a list into a string list according to separators. + + No string copies are made. The elements of the resulting string list refer to subsequences of the input string. + */ + str32_split :: proc(arena: ^arena, str: str32, separators: str32_list) -> str32_list --- +} + +//////////////////////////////////////////////////////////////////////////////// +// UTF8 encoding/decoding. +//////////////////////////////////////////////////////////////////////////////// + +// A unicode codepoint. +utf32 :: rune + +// A type representing the result of decoding of utf8-encoded codepoint. +utf8_dec :: struct { + // The decoded codepoint. + codepoint: utf32, + // The size of the utf8 sequence encoding that codepoint. + size: u32, +} + +// A type representing a contiguous range of unicode codepoints. +unicode_range :: struct { + // The first codepoint of the range. + firstCodePoint: utf32, + // The number of codepoints in the range. + count: u32, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Get the size of a utf8-encoded codepoint for the first byte of the encoded sequence. + utf8_size_from_leading_char :: proc(leadingChar: char) -> u32 --- + // Get the size of the utf8 encoding of a codepoint. + utf8_codepoint_size :: proc(codePoint: utf32) -> u32 --- + utf8_codepoint_count_for_string :: proc(string: str8) -> u64 --- + // Get the length of the utf8 encoding of a sequence of unicode codepoints. + utf8_byte_count_for_codepoints :: proc(codePoints: str32) -> u64 --- + // Get the offset of the next codepoint after a given offset, in a utf8 encoded string. + utf8_next_offset :: proc(string: str8, byteOffset: u64) -> u64 --- + // Get the offset of the previous codepoint before a given offset, in a utf8 encoded string. + utf8_prev_offset :: proc(string: str8, byteOffset: u64) -> u64 --- + // Decode a utf8 encoded codepoint. + utf8_decode :: proc(string: str8) -> utf8_dec --- + // Decode a codepoint at a given offset in a utf8 encoded string. + utf8_decode_at :: proc(string: str8, offset: u64) -> utf8_dec --- + // Encode a unicode codepoint into a utf8 sequence. + utf8_encode :: proc(dst: cstring, codePoint: utf32) -> str8 --- + // Decode a utf8 string to a string of unicode codepoints using memory passed by the caller. + utf8_to_codepoints :: proc(maxCount: u64, backing: ^utf32, string: str8) -> str32 --- + // Encode a string of unicode codepoints into a utf8 string using memory passed by the caller. + utf8_from_codepoints :: proc(maxBytes: u64, backing: cstring, codePoints: str32) -> str8 --- + // Decode a utf8 encoded string to a string of unicode codepoints using an arena. + utf8_push_to_codepoints :: proc(arena: ^arena, string: str8) -> str32 --- + // Encode a string of unicode codepoints into a utf8 string using an arena. + utf8_push_from_codepoints :: proc(arena: ^arena, codePoints: str32) -> str8 --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Input, windowing, dialogs. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Application events. +//////////////////////////////////////////////////////////////////////////////// + +// This enum defines the type events that can be sent to the application by the runtime. This determines which member of the `oc_event` union field is active. +event_type :: enum u32 { + // No event. That could be used simply to wake up the application. + NONE = 0, + // A modifier key event. This event is sent when a key such as Alt, Control, Command or Shift are pressed, released, or repeated. The `key` field contains the event's details. + KEYBOARD_MODS = 1, + // A key event. This event is sent when a normal key is pressed, released, or repeated. The `key` field contains the event's details. + KEYBOARD_KEY = 2, + // A character input event. This event is sent when an input character is produced by the keyboard. The `character` field contains the event's details. + KEYBOARD_CHAR = 3, + // A mouse button event. This is event sent when one of the mouse buttons is pressed, released, or clicked. The `key` field contains the event's details. + MOUSE_BUTTON = 4, + // A mouse move event. This is event sent when the mouse is moved. The `mouse` field contains the event's details. + MOUSE_MOVE = 5, + // A mouse wheel event. This is event sent when the mouse wheel is moved (or when a trackpad is scrolled). The `mouse` field contains the event's details. + MOUSE_WHEEL = 6, + // A mouse enter event. This event is sent when the mouse enters the application's window. The `mouse` field contains the event's details. + MOUSE_ENTER = 7, + // A mouse leave event. This event is sent when the mouse leaves the application's window. + MOUSE_LEAVE = 8, + // A clipboard paste event. This event is sent when the user uses the paste shortcut while the application window has focus. + CLIPBOARD_PASTE = 9, + // A resize event. This event is sent when the application's window is resized. The `move` field contains the event's details. + WINDOW_RESIZE = 10, + // A move event. This event is sent when the window is moved. The `move` field contains the event's details. + WINDOW_MOVE = 11, + // A focus event. This event is sent when the application gains focus. + WINDOW_FOCUS = 12, + // An unfocus event. This event is sent when the application looses focus. + WINDOW_UNFOCUS = 13, + // A hide event. This event is sent when the application's window is hidden or minimized. + WINDOW_HIDE = 14, + // A show event. This event is sent when the application's window is shown or de-minimized. + WINDOW_SHOW = 15, + // A close event. This event is sent when the window is about to be closed. + WINDOW_CLOSE = 16, + // A path drop event. This event is sent when the user drops files onto the application's window. The `paths` field contains the event's details. + PATHDROP = 17, + // A frame event. This event is sent when the application should render a frame. + FRAME = 18, + // A quit event. This event is sent when the application has been requested to quit. + QUIT = 19, +} + +// This enum describes the actions that can happen to a key. +key_action :: enum u32 { + // No action happened on that key. + NO_ACTION = 0, + // The key was pressed. + PRESS = 1, + // The key was released. + RELEASE = 2, + // The key was maintained pressed at least for the system's key repeat period. + REPEAT = 3, +} + +// A code representing a key's physical location. This is independent of the system's keyboard layout. +scan_code :: enum u32 { + UNKNOWN = 0, + SPACE = 32, + APOSTROPHE = 39, + COMMA = 44, + MINUS = 45, + PERIOD = 46, + SLASH = 47, + _0 = 48, + _1 = 49, + _2 = 50, + _3 = 51, + _4 = 52, + _5 = 53, + _6 = 54, + _7 = 55, + _8 = 56, + _9 = 57, + SEMICOLON = 59, + EQUAL = 61, + LEFT_BRACKET = 91, + BACKSLASH = 92, + RIGHT_BRACKET = 93, + GRAVE_ACCENT = 96, + A = 97, + B = 98, + C = 99, + D = 100, + E = 101, + F = 102, + G = 103, + H = 104, + I = 105, + J = 106, + K = 107, + L = 108, + M = 109, + N = 110, + O = 111, + P = 112, + Q = 113, + R = 114, + S = 115, + T = 116, + U = 117, + V = 118, + W = 119, + X = 120, + Y = 121, + Z = 122, + WORLD_1 = 161, + WORLD_2 = 162, + ESCAPE = 256, + ENTER = 257, + TAB = 258, + BACKSPACE = 259, + INSERT = 260, + DELETE = 261, + RIGHT = 262, + LEFT = 263, + DOWN = 264, + UP = 265, + PAGE_UP = 266, + PAGE_DOWN = 267, + HOME = 268, + END = 269, + CAPS_LOCK = 280, + SCROLL_LOCK = 281, + NUM_LOCK = 282, + PRINT_SCREEN = 283, + PAUSE = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + KP_0 = 320, + KP_1 = 321, + KP_2 = 322, + KP_3 = 323, + KP_4 = 324, + KP_5 = 325, + KP_6 = 326, + KP_7 = 327, + KP_8 = 328, + KP_9 = 329, + KP_DECIMAL = 330, + KP_DIVIDE = 331, + KP_MULTIPLY = 332, + KP_SUBTRACT = 333, + KP_ADD = 334, + KP_ENTER = 335, + KP_EQUAL = 336, + LEFT_SHIFT = 340, + LEFT_CONTROL = 341, + LEFT_ALT = 342, + LEFT_SUPER = 343, + RIGHT_SHIFT = 344, + RIGHT_CONTROL = 345, + RIGHT_ALT = 346, + RIGHT_SUPER = 347, + MENU = 348, + COUNT = 349, +} + +// A code identifying a key. The physical location of the key corresponding to a given key code depends on the system's keyboard layout. +key_code :: enum u32 { + UNKNOWN = 0, + SPACE = 32, + APOSTROPHE = 39, + COMMA = 44, + MINUS = 45, + PERIOD = 46, + SLASH = 47, + _0 = 48, + _1 = 49, + _2 = 50, + _3 = 51, + _4 = 52, + _5 = 53, + _6 = 54, + _7 = 55, + _8 = 56, + _9 = 57, + SEMICOLON = 59, + EQUAL = 61, + LEFT_BRACKET = 91, + BACKSLASH = 92, + RIGHT_BRACKET = 93, + GRAVE_ACCENT = 96, + A = 97, + B = 98, + C = 99, + D = 100, + E = 101, + F = 102, + G = 103, + H = 104, + I = 105, + J = 106, + K = 107, + L = 108, + M = 109, + N = 110, + O = 111, + P = 112, + Q = 113, + R = 114, + S = 115, + T = 116, + U = 117, + V = 118, + W = 119, + X = 120, + Y = 121, + Z = 122, + WORLD_1 = 161, + WORLD_2 = 162, + ESCAPE = 256, + ENTER = 257, + TAB = 258, + BACKSPACE = 259, + INSERT = 260, + DELETE = 261, + RIGHT = 262, + LEFT = 263, + DOWN = 264, + UP = 265, + PAGE_UP = 266, + PAGE_DOWN = 267, + HOME = 268, + END = 269, + CAPS_LOCK = 280, + SCROLL_LOCK = 281, + NUM_LOCK = 282, + PRINT_SCREEN = 283, + PAUSE = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + KP_0 = 320, + KP_1 = 321, + KP_2 = 322, + KP_3 = 323, + KP_4 = 324, + KP_5 = 325, + KP_6 = 326, + KP_7 = 327, + KP_8 = 328, + KP_9 = 329, + KP_DECIMAL = 330, + KP_DIVIDE = 331, + KP_MULTIPLY = 332, + KP_SUBTRACT = 333, + KP_ADD = 334, + KP_ENTER = 335, + KP_EQUAL = 336, + LEFT_SHIFT = 340, + LEFT_CONTROL = 341, + LEFT_ALT = 342, + LEFT_SUPER = 343, + RIGHT_SHIFT = 344, + RIGHT_CONTROL = 345, + RIGHT_ALT = 346, + RIGHT_SUPER = 347, + MENU = 348, + COUNT = 349, +} + +keymod_flag :: enum u32 { + ALT = 0, + SHIFT, + CTRL, + CMD, + MAIN_MODIFIER, +} +keymod_flags :: bit_set[keymod_flag; u32] + +// A code identifying a mouse button. +mouse_button :: enum u32 { + LEFT = 0, + RIGHT = 1, + MIDDLE = 2, + EXT1 = 3, + EXT2 = 4, + BUTTON_COUNT = 5, +} + +// A structure describing a key event or a mouse button event. +key_event :: struct { + // The action that was done on the key. + action: key_action, + // The scan code of the key. Only valid for key events. + scanCode: scan_code, + // The key code of the key. Only valid for key events. + keyCode: key_code, + // The button of the mouse. Only valid for mouse button events. + button: mouse_button, + // Modifier flags indicating which modifier keys where pressed at the time of the event. + mods: keymod_flags, + // The number of clicks that where detected for the button. Only valid for mouse button events. + clickCount: u8, +} + +// A structure describing a character input event. +char_event :: struct { + // The unicode codepoint of the character. + codepoint: utf32, + // The utf8 sequence of the character. + sequence: [8]char, + // The utf8 sequence length. + seqLen: u8, +} + +// A structure describing a mouse move or a mouse wheel event. Mouse coordinates have their origin at the top-left corner of the window, with the y axis going down. +mouse_event :: struct { + // The x coordinate of the mouse. + x: f32, + // The y coordinate of the mouse. + y: f32, + // The delta from the last x coordinate of the mouse, or the scroll value along the x coordinate. + deltaX: f32, + // The delta from the last y coordinate of the mouse, or the scoll value along the y coordinate. + deltaY: f32, + // Modifier flags indicating which modifier keys where pressed at the time of the event. + mods: keymod_flags, +} + +// A structure describing a window move or resize event. +move_event :: struct { + // The position and dimension of the frame rectangle, i.e. including the window title bar and border. + frame: rect, + // The position and dimension of the content rectangle, relative to the frame rectangle. + content: rect, +} + +// A structure describing an event sent to the application. +event :: struct { + // The window in which this event happened. + window: window, + // The type of the event. This determines which member of the event union is active. + type: event_type, + using _: struct #raw_union { + key: key_event, + character: char_event, + mouse: mouse_event, + move: move_event, + paths: str8_list, + }, +} + +// This enum describes the kinds of possible file dialogs. +file_dialog_kind :: enum u32 { + // The file dialog is a save dialog. + SAVE = 0, + // The file dialog is an open dialog. + OPEN = 1, +} + +// A type for flags describing various file dialog options. +// File dialog flags. +file_dialog_flag :: enum u32 { + // This dialog allows selecting files. + FILES = 1, + // This dialog allows selecting directories. + DIRECTORIES, + // This dialog allows selecting multiple items. + MULTIPLE, + // This dialog allows creating directories. + CREATE_DIRECTORIES, +} +file_dialog_flags :: bit_set[file_dialog_flag; u32] + +// A structure describing a file dialog. +file_dialog_desc :: struct { + // The kind of file dialog, see `oc_file_dialog_kind`. + kind: file_dialog_kind, + // A combination of file dialog flags used to enable file dialog options. + flags: file_dialog_flags, + // The title of the dialog, displayed in the dialog title bar. + title: str8, + // Optional. The label of the OK button, e.g. "Save" or "Open". + okLabel: str8, + // Optional. A file handle to the root directory for the dialog. If set to zero, the root directory is the application's default data directory. + startAt: file, + // Optional. The path of the starting directory of the dialog, relative to its root directory. If set to nil, the dialog starts at its root directory. + startPath: str8, + // A list of file extensions used to restrict which files can be selected in this dialog. An empty list allows all files to be selected. Extensions should be provided without a leading dot. + filters: str8_list, +} + +// An enum identifying the button clicked by the user when a file dialog returns. +file_dialog_button :: enum u32 { + // The user clicked the "Cancel" button, or closed the dialog box. + CANCEL = 0, + // The user clicked the "OK" button. + OK = 1, +} + +// A structure describing the result of a file dialog. +file_dialog_result :: struct { + // The button clicked by the user. + button: file_dialog_button, + // The path that was selected when the user clicked the OK button. If the dialog box had the `OC_FILE_DIALOG_MULTIPLE` flag set, this is the first file of the list of selected paths. + path: str8, + // If the dialog box had the `OC_FILE_DIALOG_MULTIPLE` flag set and the user clicked the OK button, this list contains the selected paths. + selection: str8_list, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Set the title of the application's window. + window_set_title :: proc(title: str8) --- + // Set the size of the application's window. + window_set_size :: proc(size: vec2) --- + // Request the system to quit the application. + request_quit :: proc() --- + // Convert a scancode to a keycode, according to current keyboard layout. + scancode_to_keycode :: proc(scanCode: scan_code) -> key_code --- + // Put a string in the clipboard. + clipboard_set_string :: proc(string: str8) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// File input/output. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// API for opening, reading and writing files. +//////////////////////////////////////////////////////////////////////////////// + +// An opaque handle identifying an opened file. +file :: distinct u64 + +// The type of file open flags describing file open options. +// Flags for the `oc_file_open()` function. +file_open_flag :: enum u16 { + // Open the file in 'append' mode. All writes append data at the end of the file. + APPEND = 1, + // Truncate the file to 0 bytes when opening. + TRUNCATE, + // Create the file if it does not exist. + CREATE, + // If the file is a symlink, open the symlink itself instead of following it. + SYMLINK, + // If the file is a symlink, the call to open will fail. + NO_FOLLOW, + // Reserved. + RESTRICT, +} +file_open_flags :: bit_set[file_open_flag; u16] + +// This enum describes the access permissions of a file handle. +file_access_flag :: enum u16 { + // The file handle can be used for reading from the file. + READ = 1, + // The file handle can be used for writing to the file. + WRITE, +} +file_access :: bit_set[file_access_flag; u16] + +// This enum is used in `oc_file_seek()` to specify the starting point of the seek operation. +file_whence :: enum u32 { + // Set the file position relative to the beginning of the file. + SET = 0, + // Set the file position relative to the end of the file. + END = 1, + // Set the file position relative to the current position. + CURRENT = 2, +} + +// A type used to identify I/O requests. +io_req_id :: u64 + +// A type used to identify I/O operations. +// This enum declares all I/O operations. +io_op :: enum u32 { + // ['Open a file at a path relative to a given root directory.', '', " - `handle` is the handle to the root directory. If it is nil, the application's default directory is used.", ' - `size` is the size of the path, in bytes.', ' - `buffer` points to an array containing the path of the file to open, relative to the directory identified by `handle`.', ' - `open` contains the permissions and flags for the open operation.'] + OPEN_AT = 0, + // ['Close a file handle.', '', ' - `handle` is the handle to close.'] + CLOSE = 1, + // ['Get status information for a file handle.', '', ' - `handle` is the handle to stat.', ' - `size` is the size of the result buffer. It should be at least `sizeof(oc_file_status)`.', ' - `buffer` is the result buffer.'] + FSTAT = 2, + // ['Move the file position in a file.', '', ' - `handle` is the handle of the file.', ' - `offset` specifies the offset of the new position, relative to the base position specified by `whence`.', ' - `whence` determines the base position for the seek operation.'] + SEEK = 3, + // ['Read data from a file.', '', ' - `handle` is the handle of the file.', ' - `size` is the number of bytes to read.', ' - `buffer` is the result buffer. It should be big enough to hold `size` bytes.'] + READ = 4, + // ['Write data to a file.', '', ' - `handle` is the handle of the file.', ' - `size` is the number of bytes to write.', ' - `buffer` contains the data to write to the file.'] + WRITE = 5, + // ['Get the error attached to a file handle.', '', ' - `handle` is the handle of the file.'] + OC_OC_IO_ERROR = 6, +} + +// A structure describing an I/O request. +io_req :: struct { + // An identifier for the request. You can set this to any value you want. It is passed back in the `oc_io_cmp` completion and can be used to match requests and completions. + id: io_req_id, + // The requested operation. + op: io_op, + // A file handle used by some operations. + handle: file, + // An offset used by some operations. + offset: i64, + // A size indicating the capacity of the buffer pointed to by `buffer`, in bytes. + size: u64, + using _: struct #raw_union { + buffer: [^]char, + unused: u64, + }, + using _: struct #raw_union { + open: struct { + // The access permissions requested on the file to open. + rights: file_access, + // The options to use when opening the file. + flags: file_open_flags, + }, + whence: file_whence, + }, +} + +// A type identifying an I/O error. +// This enum declares all I/O error values. +io_error :: enum u32 { + // No error. + OK = 0, + // An unexpected error happened. + UNKNOWN = 1, + // The request had an invalid operation. + OP = 2, + // The request had an invalid handle. + HANDLE = 3, + // The operation was not carried out because the file handle has previous errors. + PREV = 4, + // The request contained wrong arguments. + ARG = 5, + // The operation requires permissions that the file handle doesn't have. + PERM = 6, + // The operation couldn't complete due to a lack of space in the result buffer. + SPACE = 7, + // One of the directory in the path does not exist or couldn't be traversed. + NO_ENTRY = 8, + // The file already exists. + EXISTS = 9, + // The file is not a directory. + NOT_DIR = 10, + // The file is a directory. + DIR = 11, + // There are too many opened files. + MAX_FILES = 12, + // The path contains too many symbolic links (this may be indicative of a symlink loop). + MAX_LINKS = 13, + // The path is too long. + PATH_LENGTH = 14, + // The file is too large. + FILE_SIZE = 15, + // The file is too large to be opened. + OVERFLOW = 16, + // The file is locked or the device on which it is stored is not ready. + NOT_READY = 17, + // The system is out of memory. + MEM = 18, + // The operation was interrupted by a signal. + INTERRUPT = 19, + // A physical error happened. + PHYSICAL = 20, + // The device on which the file is stored was not found. + NO_DEVICE = 21, + // One element along the path is outside the root directory subtree. + WALKOUT = 22, +} + +// A structure describing the completion of an I/O operation. +io_cmp :: struct { + // The request ID as passed in the `oc_io_req` request that generated this completion. + id: io_req_id, + // The error value for the operation. + error: io_error, + using _: struct #raw_union { + result: i64, + size: u64, + offset: i64, + handle: file, + }, +} + +// An enum identifying the type of a file. +file_type :: enum u32 { + // The file is of unknown type. + UNKNOWN = 0, + // The file is a regular file. + REGULAR = 1, + // The file is a directory. + DIRECTORY = 2, + // The file is a symbolic link. + SYMLINK = 3, + // The file is a block device. + BLOCK = 4, + // The file is a character device. + CHARACTER = 5, + // The file is a FIFO pipe. + FIFO = 6, + // The file is a socket. + SOCKET = 7, +} + +// A type describing file permissions. +file_perm_flag :: enum u16 { + OTHER_EXEC = 1, + OTHER_WRITE, + OTHER_READ, + GROUP_EXEC, + GROUP_WRITE, + GROUP_READ, + OWNER_EXEC, + OWNER_WRITE, + OWNER_READ, + STICKY_BIT, + SET_GID, + SET_UID, +} +file_perm :: bit_set[file_perm_flag; u16] + +datestamp :: struct { + seconds: i64, + fraction: u64, +} + +file_status :: struct { + uid: u64, + type: file_type, + perm: file_perm, + size: u64, + creationDate: datestamp, + accessDate: datestamp, + modificationDate: datestamp, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Send a single I/O request and wait for its completion. + io_wait_single_req :: proc(req: ^io_req) -> io_cmp --- + // Returns a `nil` file handle + file_nil :: proc() -> file --- + // Test if a file handle is `nil`. + file_is_nil :: proc(handle: file) -> bool --- + // Open a file in the applications' default directory subtree. + file_open :: proc(path: str8, rights: file_access, flags: file_open_flags) -> file --- + // Open a file in a given directory's subtree. + file_open_at :: proc(dir: file, path: str8, rights: file_access, flags: file_open_flags) -> file --- + // Close a file. + file_close :: proc(file: file) --- + // Get the current position in a file. + file_pos :: proc(file: file) -> i64 --- + // Set the current position in a file. + file_seek :: proc(file: file, offset: i64, whence: file_whence) -> i64 --- + // Write data to a file. + file_write :: proc(file: file, size: u64, buffer: [^]char) -> u64 --- + // Read from a file. + file_read :: proc(file: file, size: u64, buffer: [^]char) -> u64 --- + // Get the last error on a file handle. + file_last_error :: proc(handle: file) -> io_error --- + file_get_status :: proc(file: file) -> file_status --- + file_size :: proc(file: file) -> u64 --- + file_open_with_request :: proc(path: str8, rights: file_access, flags: file_open_flags) -> file --- +} + +//////////////////////////////////////////////////////////////////////////////// +// API for obtaining file capabilities through open/save dialogs. +//////////////////////////////////////////////////////////////////////////////// + +// An element of a list of file handles acquired through a file dialog. +file_open_with_dialog_elt :: struct { + listElt: list_elt, + file: file, +} + +// A structure describing the result of a call to `oc_file_open_with_dialog()`. +file_open_with_dialog_result :: struct { + // The button of the file dialog clicked by the user. + button: file_dialog_button, + // The file that was opened through the dialog. If the dialog had the `OC_FILE_DIALOG_MULTIPLE` flag set, this is equal to the first handle in the `selection` list. + file: file, + // If the dialog had the `OC_FILE_DIALOG_MULTIPLE` flag set, this list of `oc_file_open_with_dialog_elt` contains the handles of the opened files. + selection: list, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Open files through a file dialog. This allows the user to select files outside the root directories currently accessible to the applications, giving them a way to provide new file capabilities to the application. + file_open_with_dialog :: proc(arena: ^arena, rights: file_access, flags: file_open_flags, desc: ^file_dialog_desc) -> file_open_with_dialog_result --- +} + +//////////////////////////////////////////////////////////////////////////////// +// API for handling filesystem paths. +//////////////////////////////////////////////////////////////////////////////// + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Get a string slice of the directory part of a path. + path_slice_directory :: proc(path: str8) -> str8 --- + // Get a string slice of the file name part of a path. + path_slice_filename :: proc(path: str8) -> str8 --- + // Split a path into path elements. + path_split :: proc(arena: ^arena, path: str8) -> str8_list --- + // Join path elements to form a path. + path_join :: proc(arena: ^arena, elements: str8_list) -> str8 --- + // Append a path to another path. + path_append :: proc(arena: ^arena, parent: str8, relPath: str8) -> str8 --- + // Test wether a path is an absolute path. + path_is_absolute :: proc(path: str8) -> bool --- +} + +//////////////////////////////////////////////////////////////////////////////// +// 2D/3D rendering APIs. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// A 2D Vector Graphics API. +//////////////////////////////////////////////////////////////////////////////// + +surface :: distinct u64 + +canvas_renderer :: distinct u64 + +canvas_context :: distinct u64 + +font :: distinct u64 + +image :: distinct u64 + +gradient_blend_space :: enum u32 { + LINEAR = 0, + SRGB = 1, +} + +color_space :: enum u32 { + RGB = 0, + SRGB = 1, +} + +color :: struct { using c: [4]f32, colorSpace: color_space } + +joint_type :: enum u32 { + MITER = 0, + BEVEL = 1, + NONE = 2, +} + +cap_type :: enum u32 { + NONE = 0, + SQUARE = 1, +} + +font_metrics :: struct { + ascent: f32, + descent: f32, + lineGap: f32, + xHeight: f32, + capHeight: f32, + width: f32, +} + +glyph_metrics :: struct { + ink: rect, + advance: vec2, +} + +text_metrics :: struct { + ink: rect, + logical: rect, + advance: vec2, +} + +rect_atlas :: struct {} + +image_region :: struct { + image: image, + rect: rect, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + surface_nil :: proc() -> surface --- + surface_is_nil :: proc(surface: surface) -> bool --- + surface_destroy :: proc(surface: surface) --- + surface_get_size :: proc(surface: surface) -> vec2 --- + surface_contents_scaling :: proc(surface: surface) -> vec2 --- + surface_bring_to_front :: proc(surface: surface) --- + surface_send_to_back :: proc(surface: surface) --- + surface_get_hidden :: proc(surface: surface) -> bool --- + surface_set_hidden :: proc(surface: surface, hidden: bool) --- + color_rgba :: proc(r: f32, g: f32, b: f32, a: f32) -> color --- + color_srgba :: proc(r: f32, g: f32, b: f32, a: f32) -> color --- + color_convert :: proc(_color: color, colorSpace: color_space) -> color --- + canvas_renderer_nil :: proc() -> canvas_renderer --- + canvas_renderer_is_nil :: proc(renderer: canvas_renderer) -> bool --- + canvas_renderer_create :: proc() -> canvas_renderer --- + canvas_renderer_destroy :: proc(renderer: canvas_renderer) --- + canvas_render :: proc(renderer: canvas_renderer, _context: canvas_context, surface: surface) --- + canvas_present :: proc(renderer: canvas_renderer, surface: surface) --- + canvas_surface_create :: proc(renderer: canvas_renderer) -> surface --- + canvas_surface_swap_interval :: proc(surface: surface, swap: i32) --- + canvas_context_nil :: proc() -> canvas_context --- + canvas_context_is_nil :: proc(_context: canvas_context) -> bool --- + canvas_context_create :: proc() -> canvas_context --- + canvas_context_destroy :: proc(_context: canvas_context) --- + canvas_context_select :: proc(_context: canvas_context) -> canvas_context --- + canvas_context_set_msaa_sample_count :: proc(_context: canvas_context, sampleCount: u32) --- + font_nil :: proc() -> font --- + font_is_nil :: proc(font: font) -> bool --- + font_create_from_memory :: proc(mem: str8, rangeCount: u32, ranges: ^unicode_range) -> font --- + font_create_from_file :: proc(file: file, rangeCount: u32, ranges: ^unicode_range) -> font --- + font_create_from_path :: proc(path: str8, rangeCount: u32, ranges: ^unicode_range) -> font --- + font_destroy :: proc(font: font) --- + font_get_glyph_indices :: proc(font: font, codePoints: str32, backing: str32) -> str32 --- + font_push_glyph_indices :: proc(arena: ^arena, font: font, codePoints: str32) -> str32 --- + font_get_glyph_index :: proc(font: font, codePoint: utf32) -> u32 --- + font_get_metrics :: proc(font: font, emSize: f32) -> font_metrics --- + font_get_metrics_unscaled :: proc(font: font) -> font_metrics --- + font_get_scale_for_em_pixels :: proc(font: font, emSize: f32) -> f32 --- + font_text_metrics_utf32 :: proc(font: font, fontSize: f32, codepoints: str32) -> text_metrics --- + font_text_metrics :: proc(font: font, fontSize: f32, text: str8) -> text_metrics --- + image_nil :: proc() -> image --- + image_is_nil :: proc(a: image) -> bool --- + image_create :: proc(renderer: canvas_renderer, width: u32, height: u32) -> image --- + image_create_from_rgba8 :: proc(renderer: canvas_renderer, width: u32, height: u32, pixels: [^]u8) -> image --- + image_create_from_memory :: proc(renderer: canvas_renderer, mem: str8, flip: bool) -> image --- + image_create_from_file :: proc(renderer: canvas_renderer, file: file, flip: bool) -> image --- + image_create_from_path :: proc(renderer: canvas_renderer, path: str8, flip: bool) -> image --- + image_destroy :: proc(image: image) --- + image_upload_region_rgba8 :: proc(image: image, region: rect, pixels: [^]u8) --- + image_size :: proc(image: image) -> vec2 --- + rect_atlas_create :: proc(arena: ^arena, width: i32, height: i32) -> ^rect_atlas --- + rect_atlas_alloc :: proc(atlas: ^rect_atlas, width: i32, height: i32) -> rect --- + rect_atlas_recycle :: proc(atlas: ^rect_atlas, rect: rect) --- + image_atlas_alloc_from_rgba8 :: proc(atlas: ^rect_atlas, backingImage: image, width: u32, height: u32, pixels: [^]u8) -> image_region --- + image_atlas_alloc_from_memory :: proc(atlas: ^rect_atlas, backingImage: image, mem: str8, flip: bool) -> image_region --- + image_atlas_alloc_from_file :: proc(atlas: ^rect_atlas, backingImage: image, file: file, flip: bool) -> image_region --- + image_atlas_alloc_from_path :: proc(atlas: ^rect_atlas, backingImage: image, path: str8, flip: bool) -> image_region --- + image_atlas_recycle :: proc(atlas: ^rect_atlas, imageRgn: image_region) --- + matrix_push :: proc(_matrix: mat2x3) --- + matrix_multiply_push :: proc(_matrix: mat2x3) --- + matrix_pop :: proc() --- + matrix_top :: proc() -> mat2x3 --- + clip_push :: proc(x: f32, y: f32, w: f32, h: f32) --- + clip_pop :: proc() --- + clip_top :: proc() -> rect --- + set_color :: proc(_color: color) --- + set_color_rgba :: proc(r: f32, g: f32, b: f32, a: f32) --- + set_color_srgba :: proc(r: f32, g: f32, b: f32, a: f32) --- + set_gradient :: proc(blendSpace: gradient_blend_space, bottomLeft: color, bottomRight: color, topRight: color, topLeft: color) --- + set_width :: proc(width: f32) --- + set_tolerance :: proc(tolerance: f32) --- + set_joint :: proc(joint: joint_type) --- + set_max_joint_excursion :: proc(maxJointExcursion: f32) --- + set_cap :: proc(cap: cap_type) --- + set_font :: proc(font: font) --- + set_font_size :: proc(size: f32) --- + set_text_flip :: proc(flip: bool) --- + set_image :: proc(image: image) --- + set_image_source_region :: proc(region: rect) --- + get_color :: proc() -> color --- + get_width :: proc() -> f32 --- + get_tolerance :: proc() -> f32 --- + get_joint :: proc() -> joint_type --- + get_max_joint_excursion :: proc() -> f32 --- + get_cap :: proc() -> cap_type --- + get_font :: proc() -> font --- + get_font_size :: proc() -> f32 --- + get_text_flip :: proc() -> bool --- + get_image :: proc() -> image --- + get_image_source_region :: proc() -> rect --- + get_position :: proc() -> vec2 --- + move_to :: proc(x: f32, y: f32) --- + line_to :: proc(x: f32, y: f32) --- + quadratic_to :: proc(x1: f32, y1: f32, x2: f32, y2: f32) --- + cubic_to :: proc(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) --- + close_path :: proc() --- + glyph_outlines :: proc(glyphIndices: str32) -> rect --- + codepoints_outlines :: proc(string: str32) --- + text_outlines :: proc(string: str8) --- + clear :: proc() --- + fill :: proc() --- + stroke :: proc() --- + rectangle_fill :: proc(x: f32, y: f32, w: f32, h: f32) --- + rectangle_stroke :: proc(x: f32, y: f32, w: f32, h: f32) --- + rounded_rectangle_fill :: proc(x: f32, y: f32, w: f32, h: f32, r: f32) --- + rounded_rectangle_stroke :: proc(x: f32, y: f32, w: f32, h: f32, r: f32) --- + ellipse_fill :: proc(x: f32, y: f32, rx: f32, ry: f32) --- + ellipse_stroke :: proc(x: f32, y: f32, rx: f32, ry: f32) --- + circle_fill :: proc(x: f32, y: f32, r: f32) --- + circle_stroke :: proc(x: f32, y: f32, r: f32) --- + arc :: proc(x: f32, y: f32, r: f32, arcAngle: f32, startAngle: f32) --- + text_fill :: proc(x: f32, y: f32, text: str8) --- + image_draw :: proc(image: image, rect: rect) --- + image_draw_region :: proc(image: image, srcRegion: rect, dstRegion: rect) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// A surface for rendering using the GLES API. +//////////////////////////////////////////////////////////////////////////////// + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + gles_surface_create :: proc() -> surface --- + gles_surface_make_current :: proc(surface: surface) --- + gles_surface_swap_interval :: proc(surface: surface, interval: i32) --- + gles_surface_swap_buffers :: proc(surface: surface) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Graphical User Interface API. +//////////////////////////////////////////////////////////////////////////////// + +key_state :: struct { + lastUpdate: u64, + transitionCount: u32, + repeatCount: u32, + down: bool, + sysClicked: bool, + sysDoubleClicked: bool, + sysTripleClicked: bool, +} + +keyboard_state :: struct { + keys: [349]key_state, + mods: keymod_flags, +} + +mouse_state :: struct { + lastUpdate: u64, + posValid: bool, + pos: vec2, + delta: vec2, + wheel: vec2, + using _: struct #raw_union { + buttons: [5]key_state, + using _: struct { + left: key_state, + right: key_state, + middle: key_state, + ext1: key_state, + ext2: key_state, + }, + }, +} + +BACKING_SIZE :: 64 + +text_state :: struct { + lastUpdate: u64, + backing: [64]utf32, + codePoints: str32, +} + +clipboard_state :: struct { + lastUpdate: u64, + pastedText: str8, +} + +input_state :: struct { + frameCounter: u64, + keyboard: keyboard_state, + mouse: mouse_state, + text: text_state, + clipboard: clipboard_state, +} + +ui_key :: struct { + hash: u64, +} + +ui_axis :: enum u32 { + X = 0, + Y = 1, + COUNT = 2, +} + +ui_align :: enum u32 { + START = 0, + END = 1, + CENTER = 2, +} + +ui_layout_align :: [2]ui_align + +ui_layout :: struct { + axis: ui_axis, + spacing: f32, + margin: [2]f32, + align: ui_layout_align, +} + +ui_size_kind :: enum u32 { + TEXT = 0, + PIXELS = 1, + CHILDREN = 2, + PARENT = 3, + PARENT_MINUS_PIXELS = 4, +} + +ui_size :: struct { + kind: ui_size_kind, + value: f32, + relax: f32, + minSize: f32, +} + +ui_box_size :: [2]ui_size + +ui_box_floating :: [2]bool + +ui_style :: struct { + size: ui_box_size, + layout: ui_layout, + floating: ui_box_floating, + floatTarget: vec2, + _color: color, + bgColor: color, + borderColor: color, + font: font, + fontSize: f32, + borderSize: f32, + roundness: f32, + animationTime: f32, + animationMask: ui_style_mask, +} + +ui_palette :: struct { + red0: color, + red1: color, + red2: color, + red3: color, + red4: color, + red5: color, + red6: color, + red7: color, + red8: color, + red9: color, + orange0: color, + orange1: color, + orange2: color, + orange3: color, + orange4: color, + orange5: color, + orange6: color, + orange7: color, + orange8: color, + orange9: color, + amber0: color, + amber1: color, + amber2: color, + amber3: color, + amber4: color, + amber5: color, + amber6: color, + amber7: color, + amber8: color, + amber9: color, + yellow0: color, + yellow1: color, + yellow2: color, + yellow3: color, + yellow4: color, + yellow5: color, + yellow6: color, + yellow7: color, + yellow8: color, + yellow9: color, + lime0: color, + lime1: color, + lime2: color, + lime3: color, + lime4: color, + lime5: color, + lime6: color, + lime7: color, + lime8: color, + lime9: color, + lightGreen0: color, + lightGreen1: color, + lightGreen2: color, + lightGreen3: color, + lightGreen4: color, + lightGreen5: color, + lightGreen6: color, + lightGreen7: color, + lightGreen8: color, + lightGreen9: color, + green0: color, + green1: color, + green2: color, + green3: color, + green4: color, + green5: color, + green6: color, + green7: color, + green8: color, + green9: color, + teal0: color, + teal1: color, + teal2: color, + teal3: color, + teal4: color, + teal5: color, + teal6: color, + teal7: color, + teal8: color, + teal9: color, + cyan0: color, + cyan1: color, + cyan2: color, + cyan3: color, + cyan4: color, + cyan5: color, + cyan6: color, + cyan7: color, + cyan8: color, + cyan9: color, + lightBlue0: color, + lightBlue1: color, + lightBlue2: color, + lightBlue3: color, + lightBlue4: color, + lightBlue5: color, + lightBlue6: color, + lightBlue7: color, + lightBlue8: color, + lightBlue9: color, + blue0: color, + blue1: color, + blue2: color, + blue3: color, + blue4: color, + blue5: color, + blue6: color, + blue7: color, + blue8: color, + blue9: color, + indigo0: color, + indigo1: color, + indigo2: color, + indigo3: color, + indigo4: color, + indigo5: color, + indigo6: color, + indigo7: color, + indigo8: color, + indigo9: color, + violet0: color, + violet1: color, + violet2: color, + violet3: color, + violet4: color, + violet5: color, + violet6: color, + violet7: color, + violet8: color, + violet9: color, + purple0: color, + purple1: color, + purple2: color, + purple3: color, + purple4: color, + purple5: color, + purple6: color, + purple7: color, + purple8: color, + purple9: color, + pink0: color, + pink1: color, + pink2: color, + pink3: color, + pink4: color, + pink5: color, + pink6: color, + pink7: color, + pink8: color, + pink9: color, + grey0: color, + grey1: color, + grey2: color, + grey3: color, + grey4: color, + grey5: color, + grey6: color, + grey7: color, + grey8: color, + grey9: color, + black: color, + white: color, +} + +ui_theme :: struct { + white: color, + primary: color, + primaryHover: color, + primaryActive: color, + border: color, + fill0: color, + fill1: color, + fill2: color, + bg0: color, + bg1: color, + bg2: color, + bg3: color, + bg4: color, + text0: color, + text1: color, + text2: color, + text3: color, + sliderThumbBorder: color, + elevatedBorder: color, + roundnessSmall: f32, + roundnessMedium: f32, + roundnessLarge: f32, + palette: ^ui_palette, +} + +ui_tag :: struct { + hash: u64, +} + +ui_selector_kind :: enum u32 { + ANY = 0, + OWNER = 1, + TEXT = 2, + TAG = 3, + STATUS = 4, + KEY = 5, +} + +ui_status_flag :: enum u8 { + HOVER = 1, + HOT, + ACTIVE, + DRAGGING, +} +ui_status :: bit_set[ui_status_flag; u8] + +ui_selector_op :: enum u32 { + DESCENDANT = 0, + AND = 1, +} + +ui_selector :: struct { + listElt: list_elt, + kind: ui_selector_kind, + op: ui_selector_op, + using _: struct #raw_union { + text: str8, + key: ui_key, + tag: ui_tag, + status: ui_status, + }, +} + +ui_pattern :: struct { + l: list, +} + +ui_box :: struct { + listElt: list_elt, + children: list, + parent: ^ui_box, + overlayElt: list_elt, + bucketElt: list_elt, + key: ui_key, + frameCounter: u64, + flags: ui_flags, + string: str8, + tags: list, + drawProc: ui_box_draw_proc, + drawData: rawptr, + beforeRules: list, + afterRules: list, + targetStyle: ^ui_style, + style: ui_style, + z: u32, + floatPos: vec2, + childrenSum: [2]f32, + spacing: [2]f32, + minSize: [2]f32, + rect: rect, + sig: ^ui_sig, + fresh: bool, + closed: bool, + parentClosed: bool, + dragging: bool, + hot: bool, + active: bool, + scroll: vec2, + pressedMouse: vec2, + hotTransition: f32, + activeTransition: f32, +} + +ui_style_rule :: struct { + boxElt: list_elt, + buildElt: list_elt, + tmpElt: list_elt, + owner: ^ui_box, + pattern: ui_pattern, + mask: ui_style_mask, + style: ^ui_style, +} + +ui_sig :: struct { + box: ^ui_box, + mouse: vec2, + delta: vec2, + wheel: vec2, + pressed: bool, + released: bool, + clicked: bool, + doubleClicked: bool, + tripleClicked: bool, + rightPressed: bool, + dragging: bool, + hovering: bool, + pasted: bool, +} + +ui_box_draw_proc :: proc "c" (arg0: ^ui_box, arg1: rawptr) + +ui_flag :: enum u32 { + CLICKABLE = 0, + SCROLL_WHEEL_X, + SCROLL_WHEEL_Y, + BLOCK_MOUSE, + HOT_ANIMATION, + ACTIVE_ANIMATION, + OVERFLOW_ALLOW_X, + OVERFLOW_ALLOW_Y, + CLIP, + DRAW_BACKGROUND, + DRAW_FOREGROUND, + DRAW_BORDER, + DRAW_TEXT, + DRAW_PROC, + OVERLAY, +} +ui_flags :: bit_set[ui_flag; u32] + +MAX_INPUT_CHAR_PER_FRAME :: 64 + +ui_input_text :: struct { + count: u8 `fmt:"-"`, + codePoints: [64]utf32 `fmt:"s,count"`, +} + +ui_stack_elt :: struct { + parent: ^ui_stack_elt, + using _: struct #raw_union { + box: ^ui_box, + size: ui_size, + clip: rect, + }, +} + +ui_tag_elt :: struct { + listElt: list_elt, + tag: ui_tag, +} + +BOX_MAP_BUCKET_COUNT :: 1024 + +ui_edit_move :: enum u32 { + NONE = 0, + CHAR = 1, + WORD = 2, + LINE = 3, +} + +ui_context :: struct { + init: bool, + input: input_state, + frameCounter: u64, + frameTime: f64, + lastFrameDuration: f64, + frameArena: arena, + boxPool: pool, + boxMap: [1024]list, + root: ^ui_box, + overlay: ^ui_box, + overlayList: list, + boxStack: ^ui_stack_elt, + clipStack: ^ui_stack_elt, + nextBoxBeforeRules: list, + nextBoxAfterRules: list, + nextBoxTags: list, + z: u32, + hovered: ^ui_box, + focus: ^ui_box, + editCursor: i32, + editMark: i32, + editFirstDisplayedChar: i32, + editCursorBlinkStart: f64, + editSelectionMode: ui_edit_move, + editWordSelectionInitialCursor: i32, + editWordSelectionInitialMark: i32, + theme: ^ui_theme, +} + +ui_text_box_result :: struct { + changed: bool, + accepted: bool, + text: str8, +} + +ui_select_popup_info :: struct { + changed: bool, + selectedIndex: i32, + optionCount: i32 `fmt:"-"`, + options: [^]str8 `fmt:"s,optionCount"`, + placeholder: str8, +} + +ui_radio_group_info :: struct { + changed: bool, + selectedIndex: i32, + optionCount: i32 `fmt:"-"`, + options: [^]str8 `fmt:"s,optionCount"`, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + input_process_event :: proc(arena: ^arena, state: ^input_state, event: ^event) --- + input_next_frame :: proc(state: ^input_state) --- + key_down :: proc(state: ^input_state, key: key_code) -> bool --- + key_press_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_release_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_repeat_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_down_scancode :: proc(state: ^input_state, key: scan_code) -> bool --- + key_press_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + key_release_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + key_repeat_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + mouse_down :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_pressed :: proc(state: ^input_state, button: mouse_button) -> u8 --- + mouse_released :: proc(state: ^input_state, button: mouse_button) -> u8 --- + mouse_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_double_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_position :: proc(state: ^input_state) -> vec2 --- + mouse_delta :: proc(state: ^input_state) -> vec2 --- + mouse_wheel :: proc(state: ^input_state) -> vec2 --- + input_text_utf32 :: proc(arena: ^arena, state: ^input_state) -> str32 --- + input_text_utf8 :: proc(arena: ^arena, state: ^input_state) -> str8 --- + clipboard_pasted :: proc(state: ^input_state) -> bool --- + clipboard_pasted_text :: proc(state: ^input_state) -> str8 --- + key_mods :: proc(state: ^input_state) -> keymod_flags --- + ui_init :: proc(_context: ^ui_context) --- + ui_get_context :: proc() -> ^ui_context --- + ui_set_context :: proc(_context: ^ui_context) --- + ui_process_event :: proc(event: ^event) --- + ui_begin_frame :: proc(size: vec2, #by_ptr defaultStyle: ui_style, mask: ui_style_mask) --- + ui_end_frame :: proc() --- + ui_draw :: proc() --- + ui_set_theme :: proc(theme: ^ui_theme) --- + ui_key_make_str8 :: proc(string: str8) -> ui_key --- + ui_key_make_path :: proc(path: str8_list) -> ui_key --- + ui_box_make_str8 :: proc(string: str8, flags: ui_flags) -> ^ui_box --- + ui_box_begin_str8 :: proc(string: str8, flags: ui_flags) -> ^ui_box --- + ui_box_end :: proc() -> ^ui_box --- + ui_box_push :: proc(box: ^ui_box) --- + ui_box_pop :: proc() --- + ui_box_top :: proc() -> ^ui_box --- + ui_box_lookup_key :: proc(key: ui_key) -> ^ui_box --- + ui_box_lookup_str8 :: proc(string: str8) -> ^ui_box --- + ui_box_set_draw_proc :: proc(box: ^ui_box, _proc: ui_box_draw_proc, data: rawptr) --- + ui_box_closed :: proc(box: ^ui_box) -> bool --- + ui_box_set_closed :: proc(box: ^ui_box, closed: bool) --- + ui_box_active :: proc(box: ^ui_box) -> bool --- + ui_box_activate :: proc(box: ^ui_box) --- + ui_box_deactivate :: proc(box: ^ui_box) --- + ui_box_hot :: proc(box: ^ui_box) -> bool --- + ui_box_set_hot :: proc(box: ^ui_box, hot: bool) --- + ui_box_sig :: proc(box: ^ui_box) -> ui_sig --- + ui_tag_make_str8 :: proc(string: str8) -> ui_tag --- + ui_tag_box_str8 :: proc(box: ^ui_box, string: str8) --- + ui_tag_next_str8 :: proc(string: str8) --- + ui_apply_style_with_mask :: proc(dst: ^ui_style, src: ^ui_style, mask: ui_style_mask) --- + ui_pattern_push :: proc(arena: ^arena, pattern: ^ui_pattern, selector: ui_selector) --- + ui_pattern_all :: proc() -> ui_pattern --- + ui_pattern_owner :: proc() -> ui_pattern --- + ui_style_next :: proc(#by_ptr style: ui_style, mask: ui_style_mask) --- + ui_style_match_before :: proc(pattern: ui_pattern, #by_ptr style: ui_style, mask: ui_style_mask) --- + ui_style_match_after :: proc(pattern: ui_pattern, #by_ptr style: ui_style, mask: ui_style_mask) --- + ui_label :: proc(label: cstring) -> ui_sig --- + ui_label_str8 :: proc(label: str8) -> ui_sig --- + ui_button :: proc(label: cstring) -> ui_sig --- + ui_checkbox :: proc(name: cstring, checked: ^bool) -> ui_sig --- + ui_slider :: proc(name: cstring, value: ^f32) -> ^ui_box --- + ui_scrollbar :: proc(name: cstring, thumbRatio: f32, scrollValue: ^f32) -> ^ui_box --- + ui_tooltip :: proc(label: cstring) --- + ui_panel_begin :: proc(name: cstring, flags: ui_flags) --- + ui_panel_end :: proc() --- + ui_menu_bar_begin :: proc(name: cstring) --- + ui_menu_bar_end :: proc() --- + ui_menu_begin :: proc(label: cstring) --- + ui_menu_end :: proc() --- + ui_menu_button :: proc(label: cstring) -> ui_sig --- + ui_text_box :: proc(name: cstring, arena: ^arena, text: str8) -> ui_text_box_result --- + ui_select_popup :: proc(name: cstring, info: ^ui_select_popup_info) -> ui_select_popup_info --- + ui_radio_group :: proc(name: cstring, info: ^ui_radio_group_info) -> ui_radio_group_info --- +} + diff --git a/core/sys/posix/arpa_inet.odin b/core/sys/posix/arpa_inet.odin new file mode 100644 index 000000000..7e950c4be --- /dev/null +++ b/core/sys/posix/arpa_inet.odin @@ -0,0 +1,58 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// arpa/inet.h - definitions for internet operations + +foreign lib { + // Use Odin's native big endian types `u32be` and `u16be` instead. + // htonl :: proc(c.uint32_t) -> c.uint32_t --- + // htons :: proc(c.uint16_t) -> c.uint16_t --- + // ntohl :: proc(c.uint32_t) -> c.uint32_t --- + // ntohs :: proc(c.uint16_t) -> c.uint16_t --- + + // Use of this function is problematic because -1 is a valid address (255.255.255.255). + // Avoid its use in favor of inet_aton(), inet_pton(3), or getaddrinfo(3) which provide a cleaner way to indicate error return. + // inet_addr :: proc(cstring) -> in_addr_t --- + + // Convert the Internet host address specified by in to a string in the Internet standard dot notation. + // + // NOTE: returns a static string overwritten by further calls. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntoa.html ]] + inet_ntoa :: proc(in_addr) -> cstring --- + + // Convert a numeric address into a text string suitable for presentation. + // + // Returns `nil` and sets `errno` on failure. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html ]] + inet_ntop :: proc( + af: AF, // INET or INET6 + src: rawptr, // either ^in_addr or ^in_addr6 + dst: [^]byte, // use `INET_ADDRSTRLEN` or `INET6_ADDRSTRLEN` for minimum lengths + size: socklen_t, + ) -> cstring --- + + // Convert an address in its standard text presentation form into its numeric binary form. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html ]] + inet_pton :: proc( + af: AF, // INET or INET6 + src: cstring, + dst: rawptr, // either ^in_addr or ^in_addr6 + size: socklen_t, // size_of(dst^) + ) -> pton_result --- +} + +pton_result :: enum c.int { + AFNOSUPPORT = -1, + INVALID = 0, + SUCCESS = 1, +} diff --git a/core/sys/posix/dirent.odin b/core/sys/posix/dirent.odin new file mode 100644 index 000000000..bbb5416c5 --- /dev/null +++ b/core/sys/posix/dirent.odin @@ -0,0 +1,205 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// dirent.h - format of directory entries + +foreign lib { + /* + can be used as the comparison function for the scandir() function to sort the directory entries, d1 and d2, into alphabetical order. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]] + */ + @(link_name=LALPHASORT) + alphasort :: proc([^]^dirent, [^]^dirent) -> c.int --- + + /* + Scan the directory dir, calling the function referenced by sel on each directory entry. + + Example: + list: [^]^posix.dirent + ret := posix.scandir(#directory, &list, nil, posix.alphasort) + if ret < 0 { + panic(string(posix.strerror(posix.errno()))) + } + defer posix.free(list) + + entries := list[:ret] + for entry in entries { + log.info(entry) + posix.free(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]] + */ + @(link_name=LSCANDIR) + scandir :: proc( + dir: cstring, + sel: ^[^]^dirent, + filter: proc "c" (^dirent) -> b32 = nil, + compar: proc "c" ([^]^dirent, [^]^dirent) -> c.int = alphasort, + ) -> c.int --- + + /* + Close the directory stream referred to by the argument dirp. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/closedir.html ]] + */ + closedir :: proc(dirp: DIR) -> result --- + + /* + Return a file descriptor referring to the same directory as the dirp argument. + + // TODO: this is a macro on NetBSD? + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]] + */ + dirfd :: proc(dirp: DIR) -> FD --- + + /* + Equivalent to the opendir() function except that the directory is specified by a file descriptor + rather than by a name. + The file offset associated with the file descriptor at the time of the call determines + which entries are returned. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopendir.html ]] + */ + @(link_name="fdopendir" + INODE_SUFFIX) + fdopendir :: proc(dirp: FD) -> DIR --- + + /* + Open a directory stream corresponding to the directory named by the dirname argument. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopendir.html ]] + */ + @(link_name=LOPENDIR) + opendir :: proc(path: cstring) -> DIR --- + + /* + Returns a pointer to a structure representing the directory entry at the current position + in the directory stream specified by the argument dirp, and position the directory stream at + the next entry. + + Returns nil when the end is reached or an error occurred (which sets errno). + + Example: + posix.set_errno(.NONE) + entry := posix.readdir(dirp) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + panic(string(posix.strerror(errno))) + } else { + fmt.println("end of directory stream") + } + } else { + fmt.println(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html ]] + */ + @(link_name=LREADDIR) + readdir :: proc(dirp: DIR) -> ^dirent --- + + /* + Reset the position of the directory stream to which dirp refers to the beginning of the directory. + It shall also cause the directory stream to refer to the current state of the corresponding directory, + as a call to opendir() would have done. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rewinddir.html ]] + */ + @(link_name="rewinddir" + INODE_SUFFIX) + rewinddir :: proc(dirp: DIR) --- + + /* + The seekdir() function shall set the position of the next readdir() operation on the directory + stream specified by dirp to the position specified by loc. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/seekdir.html ]] + */ + @(link_name="seekdir" + INODE_SUFFIX) + seekdir :: proc(dirp: DIR, loc: dir_loc) --- + + /* + The telldir() function shall obtain the current location associated with the directory stream + specified by dirp. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/telldir.html ]] + */ + @(link_name="telldir" + INODE_SUFFIX) + telldir :: proc(dirp: DIR) -> dir_loc --- + + // deprecated. + // readdir_r :: proc(DIR, ^dirent, ^^dirent) -> c.int --- +} + +DIR :: distinct rawptr + +dir_loc :: c.long + +// NOTE: `d_type` is not a POSIX standard field, but all targets we support add it. +D_Type :: enum c.uint8_t { + UNKNOWN = 0, + FIFO = 1, + CHR = 2, + DIR = 4, + BLK = 6, + REG = 8, + LNK = 10, + SOCK = 12, + WHT = 14, +} + +when ODIN_OS == .NetBSD { + @(private) LALPHASORT :: "__alphasort30" + @(private) LSCANDIR :: "__scandir30" + @(private) LOPENDIR :: "__opendir30" + @(private) LREADDIR :: "__readdir30" +} else { + @(private) LALPHASORT :: "alphasort" + INODE_SUFFIX + @(private) LSCANDIR :: "scandir" + INODE_SUFFIX + @(private) LOPENDIR :: "opendir" + INODE_SUFFIX + @(private) LREADDIR :: "readdir" + INODE_SUFFIX +} + +when ODIN_OS == .Darwin { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_seekoff: c.uint64_t, /* seek offset */ + d_reclen: c.uint16_t, /* length of this record */ + d_namelen: c.uint16_t, /* length of string in d_name */ + d_type: D_Type, /* file type */ + d_name: [1024]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_off: off_t, /* directory offset of the next entry */ + d_reclen: c.uint16_t, /* length of this record */ + d_type: D_Type, /* file type */ + d_namelen: c.uint8_t, /* length of string in d_name */ + d_pad0: c.uint32_t, + d_name: [256]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else when ODIN_OS == .NetBSD { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_reclen: c.uint16_t, /* length of this record */ + d_namelen: c.uint16_t, /* length of string in d_name */ + d_type: D_Type, /* file type */ + d_name: [512]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/dlfcn.odin b/core/sys/posix/dlfcn.odin new file mode 100644 index 000000000..0ddc41337 --- /dev/null +++ b/core/sys/posix/dlfcn.odin @@ -0,0 +1,117 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + foreign import lib "system:dl" +} else { + foreign import lib "system:c" +} + +// dlfcn.h - dynamic linking + +foreign lib { + /* + inform the system that the object referenced by a handle returned from a previous dlopen() + invocation is no longer needed by the application. + + Returns: 0 on success, non-zero on failure (use dlerror() for more information) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlclose.html ]] + */ + dlclose :: proc(handle: Symbol_Table) -> c.int --- + + /* + return a null-terminated character string (with no trailing ) that describes + the last error that occurred during dynamic linking processing. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlerror.html ]] + */ + dlerror :: proc() -> cstring --- + + /* + Make the symbols (function identifiers and data object identifiers) in the executable object + file specified by file available to the calling program. + + Returns: a reference to the symbol table on success, nil on failure (use dlerror() for more information) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html ]] + */ + dlopen :: proc(file: cstring, mode: RTLD_Flags) -> Symbol_Table --- + + /* + Obtain the address of a symbol (a function identifier or a data object identifier) + defined in the symbol table identified by the handle argument. + + Returns: the address of the matched symbol on success, nil on failure (use dlerror() for more information) + + Example: + handle := posix.dlopen("/usr/home/me/libfoo.so", posix.RTLD_LOCAL + { .RTLD_LAZY }) + defer posix.dlclose(handle) + + if handle == nil { + panic(string(posix.dlerror())) + } + + foo: proc(a, b: int) -> int + foo = auto_cast posix.dlsym(handle, "foo") + + if foo == nil { + panic(string(posix.dlerror())) + } + + fmt.printfln("foo(%v, %v) == %v", 1, 2, foo(1, 2)) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html ]] + */ + dlsym :: proc(handle: Symbol_Table, name: cstring) -> rawptr --- +} + +RTLD_Flag_Bits :: enum c.int { + LAZY = log2(RTLD_LAZY), + NOW = log2(RTLD_NOW), + GLOBAL = log2(RTLD_GLOBAL), + + // NOTE: use with `posix.RTLD_LOCAL + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // LOCAL = RTLD_LOCAL + + _MAX = 31, +} +RTLD_Flags :: bit_set[RTLD_Flag_Bits; c.int] + +Symbol_Table :: distinct rawptr + +when ODIN_OS == .Darwin { + + RTLD_LAZY :: 0x1 + RTLD_NOW :: 0x2 + _RTLD_LOCAL :: 0x4 + RTLD_GLOBAL :: 0x8 + + RTLD_LOCAL :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))} + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + RTLD_LAZY :: 1 + RTLD_NOW :: 2 + _RTLD_LOCAL :: 0 + RTLD_GLOBAL :: 0x100 + + RTLD_LOCAL :: RTLD_Flags{} + +} else when ODIN_OS == .NetBSD { + + RTLD_LAZY :: 0x1 + RTLD_NOW :: 0x2 + _RTLD_LOCAL :: 0x200 + RTLD_GLOBAL :: 0x100 + + RTLD_LOCAL :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))} + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/errno.odin b/core/sys/posix/errno.odin new file mode 100644 index 000000000..4ef10aadf --- /dev/null +++ b/core/sys/posix/errno.odin @@ -0,0 +1,373 @@ +package posix + +import "core:c" +import "core:c/libc" + +// errno.h - system error numbers + +EDOM :: libc.EDOM +EILSEQ :: libc.EILSEQ +ERANGE :: libc.ERANGE + +@(no_instrumentation) +get_errno :: #force_inline proc "contextless" () -> Errno { + return (^Errno)(libc.errno())^ +} + +set_errno :: #force_inline proc "contextless" (err: Errno) { + libc.errno()^ = i32(err) +} + +errno :: proc { + get_errno, + set_errno, +} + +Errno :: enum c.int { + NONE = 0, + EDOM = EDOM, + EILSEQ = EILSEQ, + ERANGE = ERANGE, + E2BIG = E2BIG, + EACCES = EACCES, + EADDRINUSE = EADDRINUSE, + EADDRNOTAVAIL = EADDRNOTAVAIL, + EAFNOSUPPORT = EAFNOSUPPORT, + EAGAIN = EAGAIN, + EALREADY = EALREADY, + EBADF = EBADF, + EBADMSG = EBADMSG, + EBUSY = EBUSY, + ECANCELED = ECANCELED, + ECHILD = ECHILD, + ECONNABORTED = ECONNABORTED, + ECONNREFUSED = ECONNREFUSED, + ECONNRESET = ECONNRESET, + EDEADLK = EDEADLK, + EDESTADDRREQ = EDESTADDRREQ, + EDQUOT = EDQUOT, + EEXIST = EEXIST, + EFAULT = EFAULT, + EFBIG = EFBIG, + EHOSTUNREACH = EHOSTUNREACH, + EIDRM = EIDRM, + EINPROGRESS = EINPROGRESS, + EINTR = EINTR, + EINVAL = EINVAL, + EIO = EIO, + EISCONN = EISCONN, + EISDIR = EISDIR, + ELOOP = ELOOP, + EMFILE = EMFILE, + EMLINK = EMLINK, + EMSGSIZE = EMSGSIZE, + EMULTIHOP = EMULTIHOP, + ENAMETOOLONG = ENAMETOOLONG, + ENETDOWN = ENETDOWN, + ENETRESET = ENETRESET, + ENETUNREACH = ENETUNREACH, + ENFILE = ENFILE, + ENOBUFS = ENOBUFS, + ENODATA = ENODATA, + ENODEV = ENODEV, + ENOENT = ENOENT, + ENOEXEC = ENOEXEC, + ENOLCK = ENOLCK, + ENOLINK = ENOLINK, + ENOMEM = ENOMEM, + ENOMSG = ENOMSG, + ENOPROTOOPT = ENOPROTOOPT, + ENOSPC = ENOSPC, + ENOSR = ENOSR, + ENOSTR = ENOSTR, + ENOSYS = ENOSYS, + ENOTCONN = ENOTCONN, + ENOTDIR = ENOTDIR, + ENOTEMPTY = ENOTEMPTY, + ENOTRECOVERABLE = ENOTRECOVERABLE, + ENOTSOCK = ENOTSOCK, + ENOTSUP = ENOTSUP, + ENOTTY = ENOTTY, + ENXIO = ENXIO, + EOPNOTSUPP = EOPNOTSUPP, + EOVERFLOW = EOVERFLOW, + EOWNERDEAD = EOWNERDEAD, + EPERM = EPERM, + EPIPE = EPIPE, + EPROTO = EPROTO, + EPROTONOSUPPORT = EPROTONOSUPPORT, + EPROTOTYPE = EPROTOTYPE, + EROFS = EROFS, + ESPIPE = ESPIPE, + ESRCH = ESRCH, + ESTALE = ESTALE, + ETIME = ETIME, + ETIMEDOUT = ETIMEDOUT, + ETXTBSY = ETXTBSY, + EWOULDBLOCK = EWOULDBLOCK, + EXDEV = EXDEV, +} + +when ODIN_OS == .Darwin { + 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 + 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 + EAGAIN :: 35 + EWOULDBLOCK :: 35 + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: 45 + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + EOVERFLOW :: 84 + ECANCELED :: 89 + EIDRM :: 90 + ENOMSG :: 91 + EBADMSG :: 94 + EMULTIHOP :: 95 + ENODATA :: 96 + ENOLINK :: 97 + ENOSR :: 98 + ENOSTR :: 99 + EPROTO :: 100 + ETIME :: 101 + ENOTRECOVERABLE :: 104 + EOWNERDEAD :: 105 +} else when ODIN_OS == .FreeBSD { + 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 + 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 + EAGAIN :: 35 + EWOULDBLOCK :: 35 + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: 45 + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + EOVERFLOW :: 84 + EIDRM :: 82 + ENOMSG :: 83 + ECANCELED :: 85 + EBADMSG :: 89 + EMULTIHOP :: 90 + ENOLINK :: 91 + EPROTO :: 92 + ENOTRECOVERABLE :: 95 + EOWNERDEAD :: 96 + + // NOTE: not defined for freebsd + ENODATA :: -1 + ENOSR :: -1 + ENOSTR :: -1 + ETIME :: -1 +} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + 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 + 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 + EAGAIN :: 35 + EWOULDBLOCK :: 35 + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: 45 + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + + when ODIN_OS == .NetBSD { + EOVERFLOW :: 84 + EIDRM :: 82 + ENOMSG :: 83 + ECANCELED :: 87 + EBADMSG :: 88 + ENODATA :: 89 + EMULTIHOP :: 94 + ENOLINK :: 95 + EPROTO :: 96 + ENOTRECOVERABLE :: 98 + EOWNERDEAD :: 97 + ENOSR :: 90 + ENOSTR :: 91 + ETIME :: 92 + } else { + EOVERFLOW :: 87 + EIDRM :: 89 + ENOMSG :: 90 + ECANCELED :: 88 + EBADMSG :: 92 + EPROTO :: 95 + ENOTRECOVERABLE :: 93 + EOWNERDEAD :: 94 + // NOTE: not defined for openbsd + ENODATA :: -1 + EMULTIHOP :: -1 + ENOLINK :: -1 + ENOSR :: -1 + ENOSTR :: -1 + ETIME :: -1 + } + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin new file mode 100644 index 000000000..ca030a9a5 --- /dev/null +++ b/core/sys/posix/fcntl.odin @@ -0,0 +1,415 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// fcntl.h - file control options + +foreign lib { + /* + Implemented as `return open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);` + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/creat.html ]] + */ + creat :: proc(path: cstring, mode: mode_t) -> FD --- + + /* + Perform the operations on open files. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html ]] + */ + fcntl :: proc(fd: FD, cmd: FCNTL_Cmd, #c_vararg args: ..any) -> c.int --- + + /* + Establish the connection between a file and a file descriptor. + It shall create an open file description that refers to a file and a file descriptor that + refers to that open file description. The file descriptor is used by other I/O functions to + refer to that file. + The path argument points to a pathname naming the file + + Returns: -1 on failure (setting errno), a file descriptor on success. + + Example: + // The following example opens the file /tmp/file, either by creating it (if it does not already exist), + // or by truncating its length to 0 (if it does exist). In the former case, if the call creates a new file, + // the access permission bits in the file mode of the file are set to permit reading and writing by the owner, + // and to permit reading only by group members and others. + fd := posix.open("/tmp/file", { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + + // The following example uses the open() function to try to create the LOCKFILE file and open it for writing. + // Since the open() function specifies the O_EXCL flag, the call fails if the file already exists. + // In that case, the program assumes that someone else is updating the password file and exits. + fd := posix.open("/etc/ptmp", { .WRONLY, .CREAT, .EXCL }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + if fd == -1 { + fmt.println("cannot open /etc/ptmp") + } + + // The following example opens a file for writing, creating the file if it does not already exist. + // If the file does exist, the system truncates the file to zero bytes. + fd := posix.open("/etc/ptmp", { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + if fd == -1 { + fmt.println("cannot open output file") + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]] + */ + open :: proc(path: cstring, flags: O_Flags, #c_vararg mode: ..mode_t) -> FD --- + + /* + Equivalent to the open() function except in the case where path specifies a relative path. + In this case the file to be opened is determined relative to the directory associated with the + file descriptor fd instead of the current working directory. + + Returns: -1 on failure (setting errno), a file descriptor on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]] + */ + openat :: proc(fd: FD, path: cstring, flags: O_Flags, mode: mode_t = {}) -> FD --- +} + +FCNTL_Cmd :: enum c.int { + DUPFD = F_DUPFD, + DUPFD_CLOEXEC = F_DUPFD_CLOEXEC, + GETFD = F_GETFD, + SETFD = F_SETFD, + GETFL = F_GETFL, + SETFL = F_SETFL, + GETLK = F_GETLK, + SETLK = F_SETLK, + SETLKW = F_SETLKW, + GETOWN = F_GETOWN, + SETOWN = F_SETOWN, +} + +Lock_Type :: enum c.short { + RDLCK = F_RDLCK, + UNLCK = F_UNLCK, + WRLCK = F_WRLCK, +} + +// Assertions made to unify this bit set. +#assert(O_RDONLY == 0) + +O_Flag_Bits :: enum c.int { + // Sets FD_CLOEXEC on the file descriptor. + CLOEXEC = log2(O_CLOEXEC), + // If not exists, combined with DIRECTORY will cause creation of a directory, otherwise a regular file. + CREAT = log2(O_CREAT), + // Fails if the opened descriptor would not be a directory. + DIRECTORY = log2(O_DIRECTORY), + // If combined with CREAT, causes a failure if the file already exists. + EXCL = log2(O_EXCL), + // If terminal device, do not make it the controlling terminal for the process. + NOCTTY = log2(O_NOCTTY), + // Don't follow symbolic links, fail with errno ELOOP. + NOFOLLOW = log2(O_NOFOLOW), + // If exists and regular, truncate the length to 0. + TRUNC = log2(O_TRUNC), + + // NOTE: use with `posix.O_TTY_INIT + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // TTY_INIT = O_TTY_INIT, + + // Set file offset to end of file prior to each write. + APPEND = log2(O_APPEND), + // Write I/O shall complete as defined by synchronized I/O data integrity completion. + DSYNC = log2(O_DSYNC), + // Causes nonblocking behaviour in various situations. + NONBLOCK = log2(O_NONBLOCK), + // Write I/O shall complete as defined by synchronized I/O file integrity completion. + SYNC = log2(O_SYNC), + // NOTE: use with `posix.O_RSYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // RSYNC = O_RSYNC, + + // Execute only. + EXEC = log2(O_EXEC), + // Reading and writing. + RDWR = log2(O_RDWR), + // Writing only. + WRONLY = log2(O_WRONLY), + // Reading only. + // RDONLY = 0, // Default + +} +O_Flags :: bit_set[O_Flag_Bits; c.int] + +// A mask of all the access mode bits. +O_ACCMODE :: O_Flags{ .EXEC, .RDWR, .WRONLY } + +AT_Flag_Bits :: enum c.int { + EACCESS = log2(AT_EACCESS), + SYMLINK_NOFOLLOW = log2(AT_SYMLINK_NOFOLLOW), + SYMLINK_FOLLOW = log2(AT_SYMLINK_FOLLOW), + REMOVEDIR = log2(AT_REMOVEDIR), +} +AT_Flags :: bit_set[AT_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 67 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x01000000 + O_CREAT :: 0x00000200 + O_DIRECTORY :: 0x00100000 + O_EXCL :: 0x00000800 + O_NOCTTY :: 0x00020000 + O_NOFOLOW :: 0x00000100 + O_TRUNC :: 0x00000400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} + + O_APPEND :: 0x00000008 + O_DSYNC :: 0x00400000 + O_NONBLOCK :: 0x00000004 + O_SYNC :: 0x0080 + + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} + + O_EXEC :: 0x40000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: O_EXEC | O_DIRECTORY + O_SEARCH :: O_Flags{ .EXEC, .DIRECTORY } + + AT_FDCWD: FD: -2 + + AT_EACCESS :: 0x0010 + AT_SYMLINK_NOFOLLOW :: 0x0020 + AT_SYMLINK_FOLLOW :: 0x0040 + AT_REMOVEDIR :: 0x0080 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } + +} else when ODIN_OS == .FreeBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 17 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x00100000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x00020000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0x00080000 + O_TTY_INIT :: O_Flags{O_Flag_Bits(log2(_O_TTY_INIT))} + + O_APPEND :: 0x0008 + O_DSYNC :: 0x01000000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} // NOTE: not defined in headers + + O_EXEC :: 0x00040000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: O_EXEC + O_SEARCH :: O_Flags{ .EXEC } + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x0100 + AT_SYMLINK_NOFOLLOW :: 0x0200 + AT_SYMLINK_FOLLOW :: 0x0400 + AT_REMOVEDIR :: 0x0800 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + l_sysid: c.int, + } + +} else when ODIN_OS == .NetBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 12 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x00400000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x0020000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} // NOTE: not defined in the headers + + O_APPEND :: 0x0008 + O_DSYNC :: 0x010000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + + _O_RSYNC :: 0x0002 + O_RSYNC :: O_Flags{O_Flag_Bits(log2(_O_RSYNC))} + + + O_EXEC :: 0x04000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: 0x00800000 + O_SEARCH :: O_Flags{O_Flag_Bits(log2(_O_SEARCH))} + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x100 + AT_SYMLINK_NOFOLLOW :: 0x200 + AT_SYMLINK_FOLLOW :: 0x400 + AT_REMOVEDIR :: 0x800 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } +} else when ODIN_OS == .OpenBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 10 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x10000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x20000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} // NOTE: not defined in the headers + + O_APPEND :: 0x0008 + O_DSYNC :: 0x010000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + + _O_RSYNC :: O_SYNC + O_RSYNC :: O_Flags{ .SYNC } + + O_EXEC :: 0x04000000 // NOTE: not defined in the headers + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: 0 + O_SEARCH :: O_Flags{} // NOTE: not defined in the headers + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x01 + AT_SYMLINK_NOFOLLOW :: 0x02 + AT_SYMLINK_FOLLOW :: 0x04 + AT_REMOVEDIR :: 0x08 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/fnmatch.odin b/core/sys/posix/fnmatch.odin new file mode 100644 index 000000000..9e54972e7 --- /dev/null +++ b/core/sys/posix/fnmatch.odin @@ -0,0 +1,58 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// fnmatch.h - filename-matching types + +foreign lib { + /* + Match patterns as described in XCU [[ Patterns Matching a Single Character; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 ]] + // and [[ Patterns Matching Multiple Characters; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_02 ]]. + It checks the string specified by the string argument to see if it matches the pattern specified by the pattern argument. + + Returns: 0 when matched. if there is no match, fnmatch() shall return FNM_NOMATCH. Non-zero on other errors. + + Example: + assert(posix.fnmatch("*.odin", "foo.odin", {}) == 0) + assert(posix.fnmatch("*.txt", "foo.odin", {}) == posix.FNM_NOMATCH) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html ]] + */ + fnmatch :: proc(pattern: cstring, string: cstring, flags: FNM_Flags) -> c.int --- +} + +FNM_Flag_Bits :: enum c.int { + // A character ( '/' ) in string shall be explicitly matched by a in pattern; + // it shall not be matched by either the or special characters, + // nor by a bracket expression. + PATHNAME = log2(FNM_PATHNAME), + + // A leading ( '.' ) in string shall match a in pattern; + // as described by rule 2 in XCU [[ Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]] + // where the location of "leading" is indicated by the value of PATHNAME: + // 1. If PATHNAME is set, a is "leading" if it is the first character in string or if it immediately follows a . + // 2. If PATHNAME is not set, a is "leading" only if it is the first character of string. + PERIOD = log2(FNM_PERIOD), + + // A character shall be treated as an ordinary character. + NOESCAPE = log2(FNM_NOESCAPE), +} +FNM_Flags :: bit_set[FNM_Flag_Bits; c.int] + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + FNM_NOMATCH :: 1 + + FNM_PATHNAME :: 0x02 + FNM_PERIOD :: 0x04 + FNM_NOESCAPE :: 0x01 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/glob.odin b/core/sys/posix/glob.odin new file mode 100644 index 000000000..4f41d83db --- /dev/null +++ b/core/sys/posix/glob.odin @@ -0,0 +1,179 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// glob.h - pathname pattern-matching types + +foreign lib { + /* + The glob() function is a pathname generator that shall implement the rules defined in + [[ XCU Pattern Matching Notation; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13 ]], + with optional support for rule 3 in XCU [[ Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]]. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html ]] + */ + @(link_name=LGLOB) + glob :: proc( + pattern: cstring, + flags: Glob_Flags, + errfunc: proc "c" (epath: cstring, eerrno: Errno) -> b32 = nil, // Return `true` to abort the glob(). + pglob: ^glob_t, + ) -> Glob_Result --- + + /* + Free the glob results. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html ]] + */ + @(link_name=LGLOBFREE) + globfree :: proc(^glob_t) --- +} + +Glob_Flag_Bits :: enum c.int { + // Append pathnames generated to the ones from a previous call to glob(). + APPEND = log2(GLOB_APPEND), + // Make use of pglob->gl_offs. If this flag is set, pglob->gl_offs is used to specify how many null pointers to add to the beginning of pglob->gl_pathv. + // In other words, pglob->gl_pathv shall point to pglob->gl_offs null pointers, followed by pglob->gl_pathc pathname pointers, followed by a null pointer. + DOOFFS = log2(GLOB_DOOFFS), + // Cause glob() to return when it encounters a directory that it cannot open or read. Ordinarily, + // glob() continues to find matches. + ERR = log2(GLOB_ERR), + // Each pathname that is a directory that matches pattern shall have a appended. + MARK = log2(GLOB_MARK), + // Supports rule 3 in [[ XCU Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]]. + // If pattern does not match any pathname, then glob() shall return a list consisting of only pattern, + // and the number of matched pathnames is 1. + NOCHECK = log2(GLOB_NOCHECK), + // Disable backslash escaping. + NOESCAPE = log2(GLOB_NOESCAPE), + // Ordinarily, glob() sorts the matching pathnames according to the current setting of the + // LC_COLLATE category; see XBD LC_COLLATE. When this flag is used, + // the order of pathnames returned is unspecified. + NOSORT = log2(GLOB_NOSORT), +} +Glob_Flags :: bit_set[Glob_Flag_Bits; c.int] + +Glob_Result :: enum c.int { + SUCCESS = 0, + ABORTED = GLOB_ABORTED, + NOMATCH = GLOB_NOMATCH, + NOSPACE = GLOB_NOSPACE, +} + +when ODIN_OS == .NetBSD { + @(private) LGLOB :: "__glob30" + @(private) LGLOBFREE :: "__globfree30" +} else { + @(private) LGLOB :: "glob" + INODE_SUFFIX + @(private) LGLOBFREE :: "globfree" +} + +when ODIN_OS == .Darwin { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.int, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + // Non-standard alternate file system access functions: + + using _: struct #raw_union { + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + gl_errblk: proc "c" (cstring, c.int) -> c.int, + }, + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x2000 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + // Non-standard alternate file system access functions: + + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x2000 when ODIN_OS == .FreeBSD else 0x0100 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else when ODIN_OS == .OpenBSD { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + gl_statv: [^]stat_t, + + // Non-standard alternate file system access functions: + + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x1000 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/grp.odin b/core/sys/posix/grp.odin new file mode 100644 index 000000000..c8a39de6a --- /dev/null +++ b/core/sys/posix/grp.odin @@ -0,0 +1,130 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// grp.h - group structure + +foreign lib { + /* + Closes the group database. + + Checking status would be done by setting errno to 0, calling this, and checking errno. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + endgrent :: proc() --- + + /* + Rewinds the group database so getgrent() returns the first entry again. + + Checking status would be done by setting errno to 0, calling this, and checking errno. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + setgrent :: proc() --- + + /* + Returns a pointer to an entry of the group database. + + Opens the group database if it isn't. + + Returns: nil on failure (setting errno) or EOF (not setting errno), the entry otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + getgrent :: proc() -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Returns: nil (setting errno) on failure, a pointer to the entry on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrgid.html ]] + */ + getgrgid :: proc(gid: gid_t) -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Updates grp with the matching entry and stores it (or a nil pointer (setting errno)) into result. + + Strings are allocated into the given buffer, you can call `sysconf(._GETGR_R_SIZE_MAX)` for an appropriate size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrgid.html ]] + */ + getgrgid_r :: proc(gid: gid_t, grp: ^group, buffer: [^]byte, bufsize: c.size_t, result: ^^group) -> Errno --- + + /* + Searches for an entry with a matching gid in the group database. + + Returns: nil (setting errno) on failure, a pointer to the entry on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html ]] + */ + getgrnam :: proc(name: cstring) -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Updates grp with the matching entry and stores it (or a nil pointer (setting errno)) into result. + + Strings are allocated into the given buffer, you can call `sysconf(._GETGR_R_SIZE_MAX)` for an appropriate size. + + Example: + length := posix.sysconf(._GETGR_R_SIZE_MAX) + if length == -1 { + length = 1024 + } + + result: posix.group + resultp: ^posix.group + + e: posix.Errno + + buffer: [dynamic]byte + defer delete(buffer) + + for { + mem_err := resize(&buffer, length) + assert(mem_err == nil) + + e = posix.getgrnam_r("nobody", &result, raw_data(buffer), len(buffer), &resultp) + if e != .ERANGE { + break + } + + length *= 2 + assert(length > 0) + } + + if e != .NONE { + panic(string(posix.strerror(e))) + } + + fmt.println(result) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html ]] + */ + getgrnam_r :: proc(name: cstring, grp: ^group, buffer: [^]byte, bufsize: c.size_t, result: ^^group) -> Errno --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + gid_t :: distinct c.uint32_t + + group :: struct { + gr_name: cstring, /* [PSX] group name */ + gr_passwd: cstring, /* group password */ + gr_gid: gid_t, /* [PSX] group id */ + gr_mem: [^]cstring, /* [PSX] group members */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/iconv.odin b/core/sys/posix/iconv.odin new file mode 100644 index 000000000..59248890f --- /dev/null +++ b/core/sys/posix/iconv.odin @@ -0,0 +1,50 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + // NOTE: iconv is in a different library + foreign import lib "system:iconv" +} else { + foreign import lib "system:c" +} + +// iconv.h - codeset conversion facility + +iconv_t :: distinct rawptr + +foreign lib { + /* + Convert the sequence of characters from one codeset, in the array specified by inbuf, + into a sequence of corresponding characters in another codeset, in the array specified by outbuf. + + Returns: -1 (setting errno) on failure, the number of non-identical conversions performed on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv.html ]] + */ + iconv :: proc( + cd: iconv_t, + inbuf: ^[^]byte, + inbytesleft: ^c.size_t, + outbuf: ^[^]byte, + outbyteslen: ^c.size_t, + ) -> c.size_t --- + + /* + Deallocates the conversion descriptor cd and all other associated resources allocated by iconv_open(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv_close.html ]] + */ + iconv_close :: proc(cd: iconv_t) -> result --- + + /* + Returns a conversion descriptor that describes a conversion from the codeset specified by the + string pointed to by the fromcode argument to the codeset specified by the string pointed to by + the tocode argument. + + Returns: -1 (setting errno) on failure, a conversion descriptor on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv_open.html ]] + */ + iconv_open :: proc(tocode: cstring, fromcode: cstring) -> iconv_t --- +} diff --git a/core/sys/posix/langinfo.odin b/core/sys/posix/langinfo.odin new file mode 100644 index 000000000..24ecc917a --- /dev/null +++ b/core/sys/posix/langinfo.odin @@ -0,0 +1,285 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// langinfo.h - language information constants + +foreign lib { + /* + Return a pointer to a string containing information relevant to the particular language or + cultural area defined in the current locale. + + Returns: a string that should not be freed or modified, and that can be invalidated at any time later + + Example: + for item in posix.nl_item { + fmt.printfln("%v: %q", item, posix.nl_langinfo(item)) + } + + Possible Output: + CODESET: "US-ASCII" + D_T_FMT: "%a %b %e %H:%M:%S %Y" + D_FMT: "%m/%d/%y" + T_FMT: "%H:%M:%S" + T_FMT_AMPM: "%I:%M:%S %p" + AM_STR: "AM" + PM_STR: "PM" + DAY_1: "Sunday" + DAY_2: "Monday" + DAY_3: "Tuesday" + DAY_4: "Wednesday" + DAY_5: "Thursday" + DAY_6: "Friday" + DAY_7: "Saturday" + ABDAY_1: "Sun" + ABDAY_2: "Mon" + ABDAY_3: "Tue" + ABDAY_4: "Wed" + ABDAY_5: "Thu" + ABDAY_6: "Fri" + ABDAY_7: "Sat" + MON_1: "January" + MON_2: "February" + MON_3: "March" + MON_4: "April" + MON_5: "May" + MON_6: "June" + MON_7: "July" + MON_8: "August" + MON_9: "September" + MON_10: "October" + MON_11: "November" + MON_12: "December" + ABMON_1: "Jan" + ABMON_2: "Feb" + ABMON_3: "Mar" + ABMON_4: "Apr" + ABMON_5: "May" + ABMON_6: "Jun" + ABMON_7: "Jul" + ABMON_8: "Aug" + ABMON_9: "Sep" + ABMON_10: "Oct" + ABMON_11: "Nov" + ABMON_12: "Dec" + ERA: "" + ERA_D_FMT: "" + ERA_D_T_FMT: "" + ERA_T_FMT: "" + ALT_DIGITS: "" + RADIXCHAR: "." + THOUSEP: "" + YESEXPR: "^[yY]" + NOEXPR: "^[nN]" + CRNCYSTR: "" + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nl_langinfo.html ]] + */ + nl_langinfo :: proc(nl_item) -> cstring --- +} + +nl_item :: enum nl_item_t { + CODESET = CODESET, + D_T_FMT = D_T_FMT, + D_FMT = D_FMT, + T_FMT = T_FMT, + T_FMT_AMPM = T_FMT_AMPM, + AM_STR = AM_STR, + PM_STR = PM_STR, + DAY_1 = DAY_1, + DAY_2 = DAY_2, + DAY_3 = DAY_3, + DAY_4 = DAY_4, + DAY_5 = DAY_5, + DAY_6 = DAY_6, + DAY_7 = DAY_7, + ABDAY_1 = ABDAY_1, + ABDAY_2 = ABDAY_2, + ABDAY_3 = ABDAY_3, + ABDAY_4 = ABDAY_4, + ABDAY_5 = ABDAY_5, + ABDAY_6 = ABDAY_6, + ABDAY_7 = ABDAY_7, + MON_1 = MON_1, + MON_2 = MON_2, + MON_3 = MON_3, + MON_4 = MON_4, + MON_5 = MON_5, + MON_6 = MON_6, + MON_7 = MON_7, + MON_8 = MON_8, + MON_9 = MON_9, + MON_10 = MON_10, + MON_11 = MON_11, + MON_12 = MON_12, + ABMON_1 = ABMON_1, + ABMON_2 = ABMON_2, + ABMON_3 = ABMON_3, + ABMON_4 = ABMON_4, + ABMON_5 = ABMON_5, + ABMON_6 = ABMON_6, + ABMON_7 = ABMON_7, + ABMON_8 = ABMON_8, + ABMON_9 = ABMON_9, + ABMON_10 = ABMON_10, + ABMON_11 = ABMON_11, + ABMON_12 = ABMON_12, + ERA = ERA, + ERA_D_FMT = ERA_D_FMT, + ERA_D_T_FMT = ERA_D_T_FMT, + ERA_T_FMT = ERA_T_FMT, + ALT_DIGITS = ALT_DIGITS, + RADIXCHAR = RADIXCHAR, + THOUSEP = THOUSEP, + YESEXPR = YESEXPR, + NOEXPR = NOEXPR, + CRNCYSTR = CRNCYSTR, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + CODESET :: 0 + D_T_FMT :: 1 + D_FMT :: 2 + T_FMT :: 3 + T_FMT_AMPM :: 4 + AM_STR :: 5 + PM_STR :: 6 + + DAY_1 :: 7 + DAY_2 :: 8 + DAY_3 :: 9 + DAY_4 :: 10 + DAY_5 :: 11 + DAY_6 :: 12 + DAY_7 :: 13 + + ABDAY_1 :: 14 + ABDAY_2 :: 15 + ABDAY_3 :: 16 + ABDAY_4 :: 17 + ABDAY_5 :: 18 + ABDAY_6 :: 19 + ABDAY_7 :: 20 + + MON_1 :: 21 + MON_2 :: 22 + MON_3 :: 23 + MON_4 :: 24 + MON_5 :: 25 + MON_6 :: 26 + MON_7 :: 27 + MON_8 :: 28 + MON_9 :: 29 + MON_10 :: 30 + MON_11 :: 31 + MON_12 :: 32 + + ABMON_1 :: 33 + ABMON_2 :: 34 + ABMON_3 :: 35 + ABMON_4 :: 36 + ABMON_5 :: 37 + ABMON_6 :: 38 + ABMON_7 :: 39 + ABMON_8 :: 40 + ABMON_9 :: 41 + ABMON_10 :: 42 + ABMON_11 :: 43 + ABMON_12 :: 44 + + ERA :: 45 + ERA_D_FMT :: 46 + ERA_D_T_FMT :: 47 + ERA_T_FMT :: 48 + ALT_DIGITS :: 49 + + RADIXCHAR :: 50 + THOUSEP :: 51 + + YESEXPR :: 52 + NOEXPR :: 53 + + CRNCYSTR :: 56 + +} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + CODESET :: 51 + D_T_FMT :: 0 + D_FMT :: 1 + T_FMT :: 2 + T_FMT_AMPM :: 3 + AM_STR :: 4 + PM_STR :: 5 + + DAY_1 :: 6 + DAY_2 :: 7 + DAY_3 :: 8 + DAY_4 :: 9 + DAY_5 :: 10 + DAY_6 :: 11 + DAY_7 :: 12 + + ABDAY_1 :: 13 + ABDAY_2 :: 14 + ABDAY_3 :: 15 + ABDAY_4 :: 16 + ABDAY_5 :: 17 + ABDAY_6 :: 18 + ABDAY_7 :: 19 + + MON_1 :: 20 + MON_2 :: 21 + MON_3 :: 22 + MON_4 :: 23 + MON_5 :: 24 + MON_6 :: 25 + MON_7 :: 26 + MON_8 :: 27 + MON_9 :: 28 + MON_10 :: 29 + MON_11 :: 30 + MON_12 :: 31 + + ABMON_1 :: 32 + ABMON_2 :: 33 + ABMON_3 :: 34 + ABMON_4 :: 35 + ABMON_5 :: 36 + ABMON_6 :: 37 + ABMON_7 :: 38 + ABMON_8 :: 39 + ABMON_9 :: 40 + ABMON_10 :: 41 + ABMON_11 :: 42 + ABMON_12 :: 43 + + ERA :: 52 + ERA_D_FMT :: 53 + ERA_D_T_FMT :: 54 + ERA_T_FMT :: 55 + ALT_DIGITS :: 56 + + RADIXCHAR :: 44 + THOUSEP :: 45 + + YESEXPR :: 47 + NOEXPR :: 49 + + CRNCYSTR :: 50 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/libgen.odin b/core/sys/posix/libgen.odin new file mode 100644 index 000000000..99506797e --- /dev/null +++ b/core/sys/posix/libgen.odin @@ -0,0 +1,74 @@ +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// libgen.h - definitions for pattern matching functions + +foreign lib { + /* + Takes the pathname pointed to by path and return a pointer to the final component of the + pathname, deleting any trailing '/' characters. + + NOTE: may modify input, so don't give it string literals. + + Returns: a string that might be a modification of the input string or a static string overwritten by subsequent calls + + Example: + tests := []string{ + "usr", "usr/", "", "/", "//", "///", "/usr/", "/usr/lib", + "//usr//lib//", "/home//dwc//test", + } + + tbl: table.Table + table.init(&tbl) + table.header(&tbl, "input", "dirname", "basename") + + for test in tests { + din := strings.clone_to_cstring(test); defer delete(din) + dir := strings.clone_from_cstring(posix.dirname(din)) + + bin := strings.clone_to_cstring(test); defer delete(bin) + base := strings.clone_from_cstring(posix.basename(bin)) + table.row(&tbl, test, dir, base) + } + + table.write_plain_table(os.stream_from_handle(os.stdout), &tbl) + + Output: + +----------------+----------+--------+ + |input |dirname |basename| + +----------------+----------+--------+ + |usr |. |usr | + |usr/ |. |usr | + | |. |. | + |/ |/ |/ | + |// |/ |/ | + |/// |/ |/ | + |/usr/ |/ |usr | + |/usr/lib |/usr |lib | + |//usr//lib// |//usr |lib | + |/home//dwc//test|/home//dwc|test | + +----------------+----------+--------+ + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html ]] + */ + basename :: proc(path: cstring) -> cstring --- + + /* + Takes a string that contains a pathname, and returns a string that is a pathname of the parent + directory of that file. + + NOTE: may modify input, so don't give it string literals. + + Returns: a string that might be a modification of the input string or a static string overwritten by subsequent calls + + See example for basename(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirname.html ]] + */ + dirname :: proc(path: cstring) -> cstring --- +} diff --git a/core/sys/posix/limits.odin b/core/sys/posix/limits.odin new file mode 100644 index 000000000..7bb561215 --- /dev/null +++ b/core/sys/posix/limits.odin @@ -0,0 +1,459 @@ +package posix + +// limits.h - implementation-defined constants + +// NOTE: numerical limits are left out because Odin provides `min(T)` and `max(T)`. + +// The header shall define the following symbolic constants with the values shown. +// These are the most restrictive values for certain features on an implementation. +// A conforming implementation shall provide values no larger than these values. +// A conforming application must not require a smaller value for correct operation. + +_POSIX_CLOCKRES_MIN :: 20000000 + +// The header shall define the following symbolic constants with the values shown. +// These are the most restrictive values for certain features on an implementation conforming to +// this volume of POSIX.1-2017. +// Related symbolic constants are defined elsewhere in this volume of POSIX.1-2017 which reflect +// the actual implementation and which need not be as restrictive. For each of these limits, +// a conforming implementation shall provide a value at least this large or shall have no limit. +// A strictly conforming application must not require a larger value for correct operation. + +_POSIX_AIO_LISTIO_MAX :: 2 +_POSIX_AIO_MAX :: 1 +_POSIX_ARG_MAX :: 4096 +_POSIX_CHILD_MAX :: 25 +_POSIX_DELAYTIMER_MAX :: 32 +_POSIX_HOST_NAME_MAX :: 255 +_POSIX_LINK_MAX :: 8 +_POSIX_MAX_CANON :: 255 +_POSIX_MAX_INPUT :: 255 +_POSIX_MQ_OPEN_MAX :: 8 +_POSIX_MQ_PRIO_MAX :: 32 +_POSIX_NAME_MAX :: 14 +_POSIX_NGROUPS_MAX :: 8 +_POSIX_OPEN_MAX :: 20 +_POSIX_PATH_MAX :: 256 +_POSIX_PIPE_BUF :: 512 +_POSIX_RE_DUP_MAX :: 255 +_POSIX_RTSIG_MAX :: 8 +_POSIX_SEM_NSEMS_MAX :: 256 +_POSIX_SEM_VALUE_MAX :: 32767 +_POSIX_SS_REPL_MAX :: 4 +_POSIX_STREAM_MAX :: 8 +_POSIX_SYMLINK_MAX :: 255 +_POSIX_SYMLOOP_MAX :: 8 +_POSIX_THREAD_DESTRUCTION_ITERATIONS :: 4 +_POSIX_THREAD_KEYS_MAX :: 128 +_POSIX_THREADS_THREADS_MAX :: 64 +_POSIX_TIMER_MAX :: 32 +_POSIX_TRAXE_EVENT_NAME_MAX :: 30 +_POSIX_TRACE_NAME_MAX :: 8 +_POSIX_TRACE_SYS_MAX :: 8 +_POSIX_TRACE_USER_EVENT_MAX :: 32 +_POSIX_TTY_NAME_MAX :: 9 +_POSIX_TZNAME_MAX :: 6 +_POSIX2_BC_BASE_MAX :: 99 +_POSIX2_BC_DIM_MAX :: 2048 +_POSIX2_BC_SCALE_MAX :: 99 +_POSIX2_CHARCLASS_NAME_MAX :: 14 +_POSIX2_COLL_WEIGHTS_MAX :: 2 +_POSIX2_EXPR_NEST_MAX :: 32 +_POSIX2_LINE_MAX :: 2048 +_POSIX2_RE_DUP_MAX :: 255 +_XOPEN_IOV_MAX :: 16 +_XOPEN_NAME_MAX :: 255 +_XOPEN_PATH_MAX :: 1024 + +/* +NOTE: for full portability, usage should look something like: + + page_size: uint + when #defined(posix.PAGESIZE) { + page_size = posix.PAGESIZE + } else { + page_size = posix.sysconf(._PAGESIZE) + } +*/ + +when ODIN_OS == .Darwin { + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 1024 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 266 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + // MQ_PRIO_MAX :: sysconf(._MQ_PRIO_MAX) + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 512 + PTHREAD_STACK_MIN :: 16384 when ODIN_ARCH == .arm64 else 8192 + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 1024 + MAX_INPUT :: 1024 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 2 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 20 + +} else when ODIN_OS == .FreeBSD { + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 2 * 256 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 40 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + MQ_PRIO_MAX :: 64 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + PTHREAD_STACK_MIN :: MINSIGSTKSZ + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + // LINK_MAX :: pathconf(foo.txt", ._LINK_MAX) + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 10 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 1023 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 4096 + NL_LANGMAX :: 31 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 0 + +} else when ODIN_OS == .NetBSD { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 256 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 160 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + LOGIN_NAME_MAX :: 17 + MQ_OPEN_MAX :: 512 + MQ_PRIO_MAX :: 32 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + // PTHREAD_STACK_MIN :: sysconf(._THREAD_STACK_MIN) + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 511 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: max(i32) + BC_DIM_MAX :: 65535 + BC_SCALE_MAX :: max(i32) + BC_STRING_MAX :: max(i32) + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 20 + +} else when ODIN_OS == .OpenBSD { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 512 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 80 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + LOGIN_NAME_MAX :: 32 + MQ_OPEN_MAX :: 512 + MQ_PRIO_MAX :: 32 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + PTHREAD_STACK_MIN :: 1 << 12 + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + SEM_VALUE_MAX :: max(u32) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + SYMLOOP_MAX :: 32 + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + SYMLINK_MAX :: PATH_MAX + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: max(i32) + BC_DIM_MAX :: 65535 + BC_SCALE_MAX :: max(i32) + BC_STRING_MAX :: max(i32) + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 255 + NZERO :: 20 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/locale.odin b/core/sys/posix/locale.odin new file mode 100644 index 000000000..1f2a336b5 --- /dev/null +++ b/core/sys/posix/locale.odin @@ -0,0 +1,93 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// locale.h - category macros + +foreign lib { + /* + Sets the components of an object with the type lconv with the values appropriate for the + formatting of numeric quantities (monetary and otherwise) according to the rules of the current + locale. + + Returns: a pointer to the lconv structure, might be invalidated by subsequent calls to localeconv() and setlocale() + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localeconv.html ]] + */ + localeconv :: proc() -> ^lconv --- + + /* + Selects the appropriate piece of the global locale, as specified by the category and locale arguments, + and can be used to change or query the entire global locale or portions thereof. + + Returns: the current locale if `locale` is `nil`, the set locale otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html ]] + */ + @(link_name=LSETLOCALE) + setlocale :: proc(category: Locale_Category, locale: cstring) -> cstring --- +} + +Locale_Category :: enum c.int { + ALL = LC_ALL, + COLLATE = LC_COLLATE, + CTYPE = LC_CTYPE, + MESSAGES = LC_MESSAGES, + MONETARY = LC_MONETARY, + NUMERIC = LC_NUMERIC, + TIME = LC_TIME, +} + +when ODIN_OS == .NetBSD { + @(private) LSETLOCALE :: "__setlocale50" +} else { + @(private) LSETLOCALE :: "setlocale" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + // NOTE: All of these fields are standard ([PSX]). + lconv :: struct { + decimal_point: cstring, + thousand_sep: cstring, + grouping: cstring, + int_curr_symbol: cstring, + currency_symbol: cstring, + mon_decimal_points: cstring, + mon_thousands_sep: cstring, + mon_grouping: cstring, + positive_sign: cstring, + negative_sign: cstring, + int_frac_digits: c.char, + frac_digits: c.char, + p_cs_precedes: c.char, + p_sep_by_space: c.char, + n_cs_precedes: c.char, + n_sep_by_space: c.char, + p_sign_posn: c.char, + n_sign_posn: c.char, + int_p_cs_precedes: c.char, + int_n_cs_precedes: c.char, + int_p_sep_by_space: c.char, + int_n_sep_by_space: c.char, + int_p_sign_posn: c.char, + int_n_sign_posn: c.char, + } + + LC_ALL :: 0 + LC_COLLATE :: 1 + LC_CTYPE :: 2 + LC_MESSAGES :: 6 + LC_MONETARY :: 3 + LC_NUMERIC :: 4 + LC_TIME :: 5 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/monetary.odin b/core/sys/posix/monetary.odin new file mode 100644 index 000000000..b4f0c31ee --- /dev/null +++ b/core/sys/posix/monetary.odin @@ -0,0 +1,42 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// monetary.h - monetary types + +foreign lib { + + /* + Places characters into the array pointed to by s as controlled by the string format. + No more than maxsize bytes are placed into the array. + + Returns: -1 (setting errno) on failure, the number of bytes added to s otherwise + + Example: + posix.setlocale(.ALL, "en_US.UTF-8") + value := 123456.789 + buffer: [100]byte + size := posix.strfmon(raw_data(buffer[:]), len(buffer), "%n", value) + if int(size) == -1 { + fmt.panicf("strfmon failure: %s", posix.strerror(posix.errno())) + } + fmt.println(string(buffer[:size])) + + Output: + $123,456.79 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strfmon.html ]] + */ + strfmon :: proc( + s: [^]byte, + maxsize: c.size_t, + format: cstring, + #c_vararg args: ..any, + ) -> c.size_t --- +} diff --git a/core/sys/posix/net_if.odin b/core/sys/posix/net_if.odin new file mode 100644 index 000000000..aaeb5088a --- /dev/null +++ b/core/sys/posix/net_if.odin @@ -0,0 +1,60 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// net/if.h - sockets local interfaces + +foreign lib { + /* + Retrieve an array of name indexes. Where the last one has an index of 0 and name of nil. + + Returns: nil (setting errno) on failure, an array that should be freed with if_freenameindex otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_nameindex.html ]] + */ + if_nameindex :: proc() -> [^]if_nameindex_t --- + + /* + Returns the interface index matching the name or zero. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_nametoindex.html ]] + */ + if_nametoindex :: proc(name: cstring) -> c.uint --- + + /* + Returns the name corresponding to the index. + + ifname should be at least IF_NAMESIZE bytes in size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_indextoname.html ]] + */ + if_indextoname :: proc(ifindex: c.uint, ifname: [^]byte) -> cstring --- + + /* + Frees memory allocated by if_nameindex. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_freenameindex.html ]] + */ + if_freenameindex :: proc(ptr: ^if_nameindex_t) --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + // NOTE: `_t` suffix added due to name conflict. + + if_nameindex_t :: struct { + if_index: c.uint, /* [PSX] 1, 2, ... */ + if_name: cstring, /* [PSX] null terminated name: "le0", ... */ + } + + IF_NAMESIZE :: 16 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/netdb.odin b/core/sys/posix/netdb.odin new file mode 100644 index 000000000..7570f9a22 --- /dev/null +++ b/core/sys/posix/netdb.odin @@ -0,0 +1,443 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// netdb.h - definitions for network database operations + +foreign lib { + /* + Translate node/serv name and return a set of socket addresses and associated information to be + used in creating a socket with which to address the specified service. + + Example: + // The following (incomplete) program demonstrates the use of getaddrinfo() to obtain the + // socket address structure(s) for the service named in the program's command-line argument. + // The program then loops through each of the address structures attempting to create and bind + // a socket to the address, until it performs a successful bind(). + + args := runtime.args__ + if len(args) != 2 { + fmt.eprintfln("Usage: %s port", args[0]) + posix.exit(1) + } + + hints: posix.addrinfo + hints.ai_socktype = .DGRAM + hints.ai_flags = { .PASSIVE } + + result: ^posix.addrinfo + s := posix.getaddrinfo(nil, args[1], &hints, &result) + if s != .NONE { + fmt.eprintfln("getaddrinfo: %s", posix.gai_strerror(s)) + posix.exit(1) + } + defer posix.freeaddrinfo(result) + + // Try each address until a successful bind(). + rp: ^posix.addrinfo + for rp = result; rp != nil; rp = rp.ai_next { + sfd := posix.socket(rp.ai_family, rp.ai_socktype, rp.ai_protocol) + if sfd == -1 { + continue + } + + if posix.bind(sfd, rp.ai_addr, rp.ai_addrlen) == 0 { + // Success. + break + } + + posix.close(sfd) + } + + if rp == nil { + fmt.eprintln("Could not bind") + posix.exit(1) + } + + // Use the socket... + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html ]] + */ + getaddrinfo :: proc( + nodename: cstring, + servname: cstring, + hints: ^addrinfo, + res: ^^addrinfo, + ) -> Info_Errno --- + + /* + Frees the given address info linked list. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html ]] + */ + freeaddrinfo :: proc(ai: ^addrinfo) --- + + /* + Translate a socket address to a node name and service location. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getnameinfo.html ]] + */ + getnameinfo :: proc( + sa: ^sockaddr, salen: socklen_t, + node: [^]byte, nodelen: socklen_t, + service: [^]byte, servicelen: socklen_t, + flags: Nameinfo_Flags, + ) -> Info_Errno --- + + /* + Get a textual description for the address info errors. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gai_strerror.html ]] + */ + gai_strerror :: proc(ecode: Info_Errno) -> cstring --- + + /* + Opens a connection to the database and set the next entry to the first entry in the database. + + This reads /etc/hosts on most systems. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + sethostent :: proc(stayopen: b32) --- + + /* + Reads the next entry in the database, opening and closing a connection as necessary. + + This reads /etc/hosts on most systems. + + Example: + posix.sethostent(true) + defer posix.endhostent() + for ent := posix.gethostent(); ent != nil; ent = posix.gethostent() { + fmt.println(ent) + fmt.println(ent.h_addr_list[0][:ent.h_length]) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + gethostent :: proc() -> ^hostent --- + + /* + Closes the connection to the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + endhostent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + setnetent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setnetent(true) + defer posix.endnetent() + for ent := posix.getnetent(); ent != nil; ent = posix.getnetent() { + fmt.println(ent) + fmt.println(transmute([4]byte)ent.n_net) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetent :: proc() -> ^netent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetbyaddr :: proc(net: c.uint32_t, type: AF) -> ^netent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetbyname :: proc(name: cstring) -> ^netent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + endnetent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + setprotoent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setprotoent(true) + defer posix.endprotoent() + for ent := posix.getprotoent(); ent != nil; ent = posix.getprotoent() { + fmt.println(ent) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotoent :: proc() -> ^protoent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotobyname :: proc(name: cstring) -> ^protoent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotobynumber :: proc(proto: c.int) -> ^protoent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + endprotoent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + setservent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setservent(true) + defer posix.endservent() + for ent := posix.getservent(); ent != nil; ent = posix.getservent() { + fmt.println(ent) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservent :: proc() -> ^servent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservbyname :: proc(name: cstring, proto: cstring) -> ^servent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservbyport :: proc(port: c.int, proto: cstring) -> ^servent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + endservent :: proc() --- +} + +Addrinfo_Flag_Bits :: enum c.int { + // Socket address is intended for bind(). + PASSIVE = log2(AI_PASSIVE), + // Request for canonical name. + CANONNAME = log2(AI_CANONNAME), + // Return numeric host address as name. + NUMERICHOST = log2(AI_NUMERICHOST), + // Inhibit service name resolution. + NUMERICSERV = log2(AI_NUMERICSERV), + // If no IPv6 addresses are found, query for IPv4 addresses and return them to the + // caller as IPv4-mapped IPv6 addresses. + V4MAPPED = log2(AI_V4MAPPED), + // Query for both IPv4 and IPv6 addresses. + ALL = log2(AI_ALL), + // Query for IPv4 addresses only when an IPv4 address is configured; query for IPv6 addresses + // only when an IPv6 address is configured. + ADDRCONFIG = log2(AI_ADDRCONFIG), +} +Addrinfo_Flags :: bit_set[Addrinfo_Flag_Bits; c.int] + +Nameinfo_Flag_Bits :: enum c.int { + // Only the nodename portion of the FQDN is returned for local hosts. + NOFQDN = log2(NI_NOFQDN), + // The numeric form of the node's address is returned instead of its name. + NUMERICHOST = log2(NI_NUMERICHOST), + // Return an error if the node's name cannot be located in the database. + NAMEREQD = log2(NI_NAMEREQD), + // The numeric form of the service address is returned instead of its name. + NUMERICSERV = log2(NI_NUMERICSERV), + // For IPv6 addresses, the numeric form of the scope identifier is returned instead of its name. + NUMERICSCOPE = log2(NI_NUMERICSCOPE), + // Indicates that the service is a datagram service (SOCK_DGRAM). + DGRAM = log2(NI_DGRAM), +} +Nameinfo_Flags :: bit_set[Nameinfo_Flag_Bits; c.int] + +Info_Errno :: enum c.int { + NONE = 0, + // The name could not be resolved at this time. Future attempts may succeed. + AGAIN = EAI_AGAIN, + // The flags had an invalid value. + BADFLAGS = EAI_BADFLAGS, + // A non-recoverable error ocurred. + FAIL = EAI_FAIL, + // The address family was not recognized or the address length was invald for the specified family. + FAMILY = EAI_FAMILY, + // There was a memory allocation failure. + MEMORY = EAI_MEMORY, + // The name does not resolve for the supplied parameters. + NONAME = EAI_NONAME, + // The service passed was not recognized for the specified socket. + SERVICE = EAI_SERVICE, + // The intended socket type was not recognized. + SOCKTYPE = EAI_SOCKTYPE, + // A system error occurred. The error code can be found in errno. + SYSTEM = EAI_SYSTEM, + // An argument buffer overflowed. + OVERFLOW = EAI_OVERFLOW, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + hostent :: struct { + h_name: cstring, /* [PSX] official name of host */ + h_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + h_addrtype: AF, /* [PSX] host address type */ + h_length: c.int, /* [PSX] length of address */ + h_addr_list: [^][^]byte `fmt:"v,0"`, /* [PSX] list of addresses from name server */ + } + + netent :: struct { + n_name: cstring, /* [PSX] official name of net */ + n_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + n_addrtype: AF, /* [PSX] net address type */ + n_net: c.uint32_t, /* [PSX] network # */ + } + + protoent :: struct { + p_name: cstring, /* [PSX] official protocol name */ + p_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + p_proto: c.int, /* [PSX] protocol # */ + } + + servent :: struct { + s_name: cstring, /* [PSX] official service name */ + s_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + s_port: c.int, /* [PSX] port # */ + s_proto: cstring, /* [PSX] protocol # */ + } + + // The highest reserved port number. + IPPORT_RESERVED :: 1024 + + addrinfo :: struct { + ai_flags: Addrinfo_Flags, /* [PSX] input flags */ + ai_family: AF, /* [PSX] address family of socket */ + ai_socktype: Sock, /* [PSX] socket type */ + ai_protocol: Protocol, /* [PSX] protocol of socket */ + ai_addrlen: socklen_t, /* [PSX] length of socket address */ + ai_canonname: cstring, /* [PSX] canonical name of service location */ + ai_addr: ^sockaddr, /* [PSX] binary address */ + ai_next: ^addrinfo, /* [PSX] pointer to next in list */ + } + + when ODIN_OS == .Darwin { + + AI_PASSIVE :: 0x00000001 + AI_CANONNAME :: 0x00000002 + AI_NUMERICHOST :: 0x00000004 + AI_NUMERICSERV :: 0x00001000 + AI_V4MAPPED :: 0x00000800 + AI_ALL :: 0x00000100 + AI_ADDRCONFIG :: 0x00000400 + + NI_NOFQDN :: 0x00000001 + NI_NUMERICHOST :: 0x00000002 + NI_NAMEREQD :: 0x00000004 + NI_NUMERICSERV :: 0x00000008 + NI_NUMERICSCOPE :: 0x00000100 + NI_DGRAM :: 0x00000010 + + } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + AI_PASSIVE :: 0x00000001 + AI_CANONNAME :: 0x00000002 + AI_NUMERICHOST :: 0x00000004 + AI_NUMERICSERV :: 0x00000008 + AI_V4MAPPED :: 0x00000800 // NOTE: not implemented on netbsd + AI_ALL :: 0x00000100 // NOTE: not implemented on netbsd + AI_ADDRCONFIG :: 0x00000400 + + NI_NOFQDN :: 0x00000001 + NI_NUMERICHOST :: 0x00000002 + NI_NAMEREQD :: 0x00000004 + NI_NUMERICSERV :: 0x00000008 + NI_NUMERICSCOPE :: 0x00000010 + NI_DGRAM :: 0x00000020 + + } else when ODIN_OS == .OpenBSD { + + AI_PASSIVE :: 1 + AI_CANONNAME :: 2 + AI_NUMERICHOST :: 4 + AI_NUMERICSERV :: 16 + AI_V4MAPPED :: 0x00000800 // NOTE: not implemented + AI_ALL :: 0x00000100 // NOTE: not implemented + AI_ADDRCONFIG :: 64 + + NI_NOFQDN :: 4 + NI_NUMERICHOST :: 1 + NI_NAMEREQD :: 8 + NI_NUMERICSERV :: 2 + NI_NUMERICSCOPE :: 32 + NI_DGRAM :: 16 + } + + when ODIN_OS == .OpenBSD { + EAI_AGAIN :: -3 + EAI_BADFLAGS :: -1 + EAI_FAIL :: -4 + EAI_FAMILY :: -6 + EAI_MEMORY :: -10 + EAI_NONAME :: -2 + EAI_SERVICE :: -8 + EAI_SOCKTYPE :: -7 + EAI_SYSTEM :: -11 + EAI_OVERFLOW :: -14 + } else { + EAI_AGAIN :: 2 + EAI_BADFLAGS :: 3 + EAI_FAIL :: 4 + EAI_FAMILY :: 5 + EAI_MEMORY :: 6 + EAI_NONAME :: 8 + EAI_SERVICE :: 9 + EAI_SOCKTYPE :: 10 + EAI_SYSTEM :: 11 + EAI_OVERFLOW :: 14 + } + +}else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/netinet_in.odin b/core/sys/posix/netinet_in.odin new file mode 100644 index 000000000..3926c5288 --- /dev/null +++ b/core/sys/posix/netinet_in.odin @@ -0,0 +1,199 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// netinet/in.h - Internet address family + +foreign lib { + in6addr_any: in6_addr + in6addr_loopback: in6_addr +} + +in_port_t :: u16be +in_addr_t :: u32be + +INET_ADDRSTRLEN :: 16 +INET6_ADDRSTRLEN :: 46 + +Protocol :: enum c.int { + IP = IPPROTO_IP, + ICMP = IPPROTO_ICMP, + IPV6 = IPPROTO_IPV6, + RAW = IPPROTO_RAW, + TCP = IPPROTO_TCP, + UDP = IPPROTO_UDP, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + in_addr :: struct { + s_addr: in_addr_t, /* [PSX] big endian address */ + } + + in6_addr :: struct { + using _: struct #raw_union { + s6_addr: [16]c.uint8_t, /* [PSX] big endian address */ + __u6_addr16: [8]c.uint16_t, + __u6_addr32: [4]c.uint32_t, + }, + } + + sockaddr_in :: struct { + sin_len: c.uint8_t, + sin_family: sa_family_t, /* [PSX] AF_INET (but a smaller size) */ + sin_port: in_port_t, /* [PSX] port number */ + sin_addr: in_addr, /* [PSX] IP address */ + sin_zero: [8]c.char, + } + + sockaddr_in6 :: struct { + sin6_len: c.uint8_t, + sin6_family: sa_family_t, /* [PSX] AF_INET6 (but a smaller size) */ + sin6_port: in_port_t, /* [PSX] port number */ + sin6_flowinfo: c.uint32_t, /* [PSX] IPv6 traffic class and flow information */ + sin6_addr: in6_addr, /* [PSX] IPv6 address */ + sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ + } + + ipv6_mreq :: struct { + ipv6mr_multiaddr: in6_addr, /* [PSX] IPv6 multicast address */ + ipv6mr_interface: c.uint, /* [PSX] interface index */ + } + + IPPROTO_IP :: 0 + IPPROTO_ICMP :: 1 + IPPROTO_IPV6 :: 41 + IPPROTO_RAW :: 255 + IPPROTO_TCP :: 6 + IPPROTO_UDP :: 17 + + INADDR_ANY :: 0x00000000 + INADDR_BROADCAST :: 0xFFFFFFFF + + IPV6_JOIN_GROUP :: 12 + IPV6_LEAVE_GROUP :: 13 + IPV6_MULTICAST_HOPS :: 10 + IPV6_MULTICAST_IF :: 9 + IPV6_MULTICAST_LOOP :: 11 + IPV6_UNICAST_HOPS :: 4 + IPV6_V6ONLY :: 27 + + IN6_IS_ADDR_UNSPECIFIED :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr == 0 + } + + IN6_IS_ADDR_LOOPBACK :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^c.uint32_t)(&a.s6_addr[8])^ == 0 && + (^u32be)(&a.s6_addr[12])^ == 1 \ + ) + } + + IN6_IS_ADDR_MULTICAST :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xff + } + + IN6_IS_ADDR_LINKLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xfe && a.s6_addr[1] & 0xc0 == 0x80 + } + + IN6_IS_ADDR_SITELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xfe && a.s6_addr[1] & 0xc0 == 0xc0 + } + + IN6_IS_ADDR_V4MAPPED :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^u32be)(&a.s6_addr[8])^ == 0x0000ffff \ + ) + } + + IN6_IS_ADDR_V4COMPAT :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^c.uint32_t)(&a.s6_addr[8])^ == 0 && + (^c.uint32_t)(&a.s6_addr[12])^ != 0 && + (^u32be)(&a.s6_addr[12])^ != 1 \ + ) + } + + @(private) + __IPV6_ADDR_SCOPE_NODELOCAL :: 0x01 + @(private) + __IPV6_ADDR_SCOPE_LINKLOCAL :: 0x02 + @(private) + __IPV6_ADDR_SCOPE_SITELOCAL :: 0x05 + @(private) + __IPV6_ADDR_SCOPE_ORGLOCAL :: 0x08 + @(private) + __IPV6_ADDR_SCOPE_GLOBAL :: 0x0e + + @(private) + IPV6_ADDR_MC_FLAGS :: #force_inline proc "contextless" (a: in6_addr) -> c.uint8_t { + return a.s6_addr[1] & 0xf0 + } + + @(private) + IPV6_ADDR_MC_FLAGS_TRANSIENT :: 0x10 + @(private) + IPV6_ADDR_MC_FLAGS_PREFIX :: 0x20 + @(private) + IPV6_ADDR_MC_FLAGS_UNICAST_BASED :: IPV6_ADDR_MC_FLAGS_TRANSIENT | IPV6_ADDR_MC_FLAGS_PREFIX + + @(private) + __IPV6_ADDR_MC_SCOPE :: #force_inline proc "contextless" (a: in6_addr) -> c.uint8_t { + return a.s6_addr[1] & 0x0f + } + + IN6_IS_ADDR_MC_NODELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_NODELOCAL) \ + ) + } + + IN6_IS_ADDR_MC_LINKLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (IPV6_ADDR_MC_FLAGS(a) != IPV6_ADDR_MC_FLAGS_UNICAST_BASED) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_LINKLOCAL) \ + ) + } + + IN6_IS_ADDR_MC_SITELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_SITELOCAL) \ + ) + } + + IN6_IS_ADDR_MC_ORGLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_ORGLOCAL) \ + ) + } + + IN6_IS_ADDR_MC_GLOBAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_GLOBAL) \ + ) + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/netinet_tcp.odin b/core/sys/posix/netinet_tcp.odin new file mode 100644 index 000000000..ecd084b38 --- /dev/null +++ b/core/sys/posix/netinet_tcp.odin @@ -0,0 +1,11 @@ +package posix + +// netinet/tcp.h - definitions for the Internet Transmission Control Protocol (TCP) + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + TCP_NODELAY :: 0x01 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/poll.odin b/core/sys/posix/poll.odin new file mode 100644 index 000000000..3e825e009 --- /dev/null +++ b/core/sys/posix/poll.odin @@ -0,0 +1,78 @@ +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// poll.h - definitions for the poll() function + +foreign lib { + /* + For each pointer in fds, poll() shall examine the given descriptor for the events. + poll will identify on which descriptors writes or reads can be done. + + Returns: -1 (setting errno) on failure, 0 on timeout, the amount of fds that have been changed on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html ]] + */ + poll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: c.int) -> c.int --- +} + +nfds_t :: c.uint + +Poll_Event_Bits :: enum c.short { + // Data other than high-priority data may be read without blocking. + IN = log2(POLLIN), + // Normal data may be read without blocking. + RDNORM = log2(POLLRDNORM), + // Priority data may be read without blocking. + RDBAND = log2(POLLRDBAND), + // High priority data may be read without blocking. + PRI = log2(POLLPRI), + + // Normal data may be written without blocking. + OUT = log2(POLLOUT), + // Equivalent to POLLOUT. + WRNORM = log2(POLLWRNORM), + // Priority data may be written. + WRBAND = log2(POLLWRBAND), + + // An error has occurred (revents only). + ERR = log2(POLLERR), + // Device hsa been disconnected (revents only). + HUP = log2(POLLHUP), + // Invalid fd member (revents only). + NVAL = log2(POLLNVAL), +} +Poll_Event :: bit_set[Poll_Event_Bits; c.short] + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + pollfd :: struct { + fd: FD, /* [PSX] the following descriptor being polled */ + events: Poll_Event, /* [PSX] the input event flags */ + revents: Poll_Event, /* [PSX] the output event flags */ + } + + POLLIN :: 0x0001 + POLLRDNORM :: 0x0040 + POLLRDBAND :: 0x0080 + POLLPRI :: 0x0002 + POLLOUT :: 0x0004 + POLLWRNORM :: POLLOUT + POLLWRBAND :: 0x0100 + + POLLERR :: 0x0008 + POLLHUP :: 0x0010 + POLLNVAL :: 0x0020 + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/posix.odin b/core/sys/posix/posix.odin new file mode 100644 index 000000000..5cb4122a6 --- /dev/null +++ b/core/sys/posix/posix.odin @@ -0,0 +1,70 @@ +/* +Bindings for most POSIX APIs. + +APIs that have been left out are due to not being useful, +being fully replaced (and better) by other Odin packages, +or when one of the targets hasn't implemented the API or option. + +The struct fields that are cross-platform are documented with `[PSX]`. +Accessing these fields on one target should be the same on others. +Other fields are implementation specific. + +Most macros have been reimplemented in Odin with inlined functions. + +Unimplemented headers: +- aio.h +- complex.h | See `core:c/libc` and our own complex types +- cpio.h +- ctype.h | See `core:c/libc` for most of it +- ndbm.h +- fenv.h +- float.h +- fmtmsg.h +- ftw.h +- semaphore.h | See `core:sync` +- inttypes.h | See `core:c` +- iso646.h | Impossible +- math.h | See `core:c/libc` +- mqueue.h | Targets don't seem to have implemented it +- regex.h | See `core:regex` +- search.h | Not useful in Odin +- spawn.h | Use `fork`, `execve`, etc. +- stdarg.h | See `core:c/libc` +- stdint.h | See `core:c` +- stropts.h +- syslog.h +- pthread.h | Only the actual threads API is bound, see `core:sync` for synchronization primitives +- string.h | Most of this is not useful in Odin, only a select few symbols are bound +- tar.h +- tgmath.h +- trace.h +- wchar.h +- wctype.h + +*/ +package posix + +import "base:intrinsics" + +import "core:c" + +result :: enum c.int { + // Use `errno` and `strerror` for more information. + FAIL = -1, + // Operation succeeded. + OK = 0, +} + +FD :: distinct c.int + +@(private) +log2 :: intrinsics.constant_log2 + +when ODIN_OS == .Darwin && ODIN_ARCH == .amd64 { + @(private) + INODE_SUFFIX :: "$INODE64" +} else { + @(private) + INODE_SUFFIX :: "" +} + diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin new file mode 100644 index 000000000..e264f6f6c --- /dev/null +++ b/core/sys/posix/pthread.odin @@ -0,0 +1,518 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + foreign import lib "system:pthread" +} else { + foreign import lib "system:c" +} + +// pthread.h - threads + +// NOTE: mutexes, rwlock, condition variables, once and barriers are left out in favour of `core:sync`. + +foreign lib { + /* + Initializes a thread attributes object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_init.html ]] + */ + pthread_attr_init :: proc(attr: ^pthread_attr_t) -> Errno --- + + /* + Destroys a thread attributes object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_init.html ]] + */ + pthread_attr_destroy :: proc(attr: ^pthread_attr_t) -> Errno --- + + /* + The detachstate attribute controls whether the thread is created in a detached state. + If the thread is created detached, then use of the ID of the newly created thread is an error. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getdetachstate.html ]] + */ + pthread_attr_getdetachstate :: proc(attr: ^pthread_attr_t, detachstate: ^Detach_State) -> Errno --- + + /* + The detachstate attribute controls whether the thread is created in a detached state. + If the thread is created detached, then use of the ID of the newly created thread is an error. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getdetachstate.html ]] + */ + pthread_attr_setdetachstate :: proc(attr: ^pthread_attr_t, detachstate: Detach_State) -> Errno --- + + /* + The guardsize attribute controls the size of the guard area for the created thread's stack. + The guardsize attribute provides protection against overflow of the stack pointer. + If a thread's stack is created with guard protection, the implementation allocates extra memory + at the overflow end of the stack as a buffer against stack overflow of the stack pointer. + If an application overflows into this buffer an error shall result (possibly in a SIGSEGV signal being delivered to the thread). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setguardsize.html ]] + */ + pthread_attr_getguardsize :: proc(attr: ^pthread_attr_t, guardsize: ^c.size_t) -> Errno --- + + /* + The guardsize attribute controls the size of the guard area for the created thread's stack. + The guardsize attribute provides protection against overflow of the stack pointer. + If a thread's stack is created with guard protection, the implementation allocates extra memory + at the overflow end of the stack as a buffer against stack overflow of the stack pointer. + If an application overflows into this buffer an error shall result (possibly in a SIGSEGV signal being delivered to the thread). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setguardsize.html ]] + */ + pthread_attr_setguardsize :: proc(attr: ^pthread_attr_t, guardsize: c.size_t) -> Errno --- + + /* + When the attributes objects are used by pthread_create(), the inheritsched attribute determines + how the other scheduling attributes of the created thread shall be set. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setinheritsched.html ]] + */ + pthread_attr_getinheritsched :: proc(attr: ^pthread_attr_t, inheritsched: ^Inherit_Sched) -> Errno --- + + /* + When the attributes objects are used by pthread_create(), the inheritsched attribute determines + how the other scheduling attributes of the created thread shall be set. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setinheritsched.html ]] + */ + pthread_attr_setinheritsched :: proc(attr: ^pthread_attr_t, inheritsched: Inherit_Sched) -> Errno --- + + /* + Gets the scheduling param. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setschedparam.html ]] + */ + pthread_attr_getschedparam :: proc(attr: ^pthread_attr_t, param: ^sched_param) -> Errno --- + + /* + Sets the scheduling param. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setschedparam.html ]] + */ + pthread_attr_setschedparam :: proc(attr: ^pthread_attr_t, param: ^sched_param) -> Errno --- + + /* + Gets the scheduling poicy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getschedpolicy.html ]] + */ + pthread_attr_getschedpolicy :: proc(attr: ^pthread_attr_t, policy: ^Sched_Policy) -> Errno --- + + /* + Sets the scheduling poicy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getschedpolicy.html ]] + */ + pthread_attr_setschedpolicy :: proc(attr: ^pthread_attr_t, policy: Sched_Policy) -> Errno --- + + /* + Gets the contention scope. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] + */ + pthread_attr_getscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + + /* + Sets the contention scope. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] + */ + pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + + /* + Get the area of storage to be used for the created thread's stack. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstack.html ]] + */ + pthread_attr_getstack :: proc(attr: ^pthread_attr_t, stackaddr: ^[^]byte, stacksize: ^c.size_t) -> Errno --- + + /* + Specify the area of storage to be used for the created thread's stack. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstack.html ]] + */ + pthread_attr_setstack :: proc(attr: ^pthread_attr_t, stackaddr: [^]byte, stacksize: c.size_t) -> Errno --- + + /* + Gets the stack size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstacksize.html ]] + */ + pthread_attr_getstacksize :: proc(attr: ^pthread_attr_t, stacksize: ^c.size_t) -> Errno --- + + /* + Sets the stack size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstacksize.html ]] + */ + pthread_attr_setstacksize :: proc(attr: ^pthread_attr_t, stacksize: c.size_t) -> Errno --- + + /* + Register fork handlers to be called before and after fork(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html ]] + */ + pthread_atfork :: proc(prepare: proc "c" (), parent: proc "c" (), child: proc "c" ()) -> Errno --- + + + /* + Cancel the execution of a thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cancel.html ]] + */ + pthread_cancel :: proc(thread: pthread_t) -> Errno --- + + /* + Creates a new thread with the given attributes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html ]] + */ + pthread_create :: proc( + thread: ^pthread_t, + attr: ^pthread_attr_t, + start_routine: proc "c" (arg: rawptr) -> rawptr, + arg: rawptr, + ) -> Errno --- + + + /* + Indicate that storage for the thread can be reclaimed when the thread terminates. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_detach.html ]] + */ + pthread_detach :: proc(thread: pthread_t) -> Errno --- + + /* + Compare thread IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_equal.html ]] + */ + pthread_equal :: proc(t1: pthread_t, t2: pthread_t) -> b32 --- + + /* + Terminates the calling thread and make the given value available to any successfull join calls. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_exit.html ]] + */ + pthread_exit :: proc(value_ptr: rawptr) -> ! --- + + /* + Gets the current concurrency hint. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html ]] + */ + pthread_getconcurrency :: proc() -> c.int --- + + /* + Sets the current desired concurrency hint. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html ]] + */ + pthread_setconcurrency :: proc(new_level: c.int) -> Errno --- + + /* + Access a thread CPU-time clock. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getcpuclockid.html ]] + */ + pthread_getcpuclockid :: proc(thread_id: pthread_t, clock_id: ^clockid_t) -> Errno --- + + /* + Gets the scheduling policy and parameters. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getschedparam.html ]] + */ + pthread_getschedparam :: proc(thread: pthread_t, policy: ^Sched_Policy, param: ^sched_param) -> Errno --- + + /* + Sets the scheduling policy and parameters. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getschedparam.html ]] + */ + pthread_setschedparam :: proc(thread: pthread_t, policy: Sched_Policy, param: ^sched_param) -> Errno --- + + /* + Creates a thread-specific data key visible to all threads in the process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_create.html ]] + */ + pthread_key_create :: proc(key: ^pthread_key_t, destructor: proc "c" (value: rawptr) = nil) -> Errno --- + + /* + Deletes a thread-specific data key visible to all threads in the process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_delete.html ]] + */ + pthread_key_delete :: proc(key: pthread_key_t) -> Errno --- + + /* + Returns the value currently bound to the specified key on behalf of the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getspecific.html ]] + */ + pthread_getspecific :: proc(key: pthread_key_t) -> rawptr --- + + /* + Sets the value currently bound to the specified key on behalf of the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getspecific.html ]] + */ + pthread_setspecific :: proc(key: pthread_key_t, value: rawptr) -> Errno --- + + /* + Suspends execution of the calling thread until the target thread terminates. + + Example: + ar: [10_000]i32 + + sb1 := ar[:5_000] + sb2 := ar[5_000:] + + th1, th2: posix.pthread_t + + posix.pthread_create(&th1, nil, incer, &sb1) + posix.pthread_create(&th2, nil, incer, &sb2) + + posix.pthread_join(th1) + posix.pthread_join(th2) + + incer :: proc "c" (arg: rawptr) -> rawptr { + sb := (^[]i32)(arg) + for &val in sb { + val += 1 + } + + return nil + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_join.html ]] + */ + pthread_join :: proc(thread: pthread_t, value_ptr: ^rawptr = nil) -> Errno --- + + /* + Get the calling thread ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_self.html ]] + */ + pthread_self :: proc() -> pthread_t --- + + /* + Atomically set the calling thread's cancelability and return the previous value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setcancelstate.html ]] + */ + pthread_setcancelstate :: proc(state: Cancel_State, oldstate: ^Cancel_State) -> Errno --- + + /* + Atomically set the calling thread's cancel type and return the previous value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setcancelstate.html ]] + */ + pthread_setcanceltype :: proc(type: Cancel_Type, oldtype: ^Cancel_Type) -> Errno --- + + + /* + Creates a cancellation point in the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_testcancel.html ]] + */ + pthread_testcancel :: proc() --- + + /* + Sets the scheduling priority for the thread given. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setschedprio.html ]] + */ + pthread_setschedprio :: proc(thread: pthread_t, prio: c.int) -> Errno --- +} + +Detach_State :: enum c.int { + // Causes all threads to be in the joinable state. + CREATE_JOINABLE = PTHREAD_CREATE_JOINABLE, + // Causes all threads to be in the detached state. + CREATE_DETACHED = PTHREAD_CREATE_DETACHED, +} + +Inherit_Sched :: enum c.int { + // Threads inherit from the creating thread. + INHERIT_SCHED = PTHREAD_INHERIT_SCHED, + // Threads scheduling shall be set to the corresponding values from the attributes object. + EXPLICIT_SCHED = PTHREAD_EXPLICIT_SCHED, +} + +Thread_Scope :: enum c.int { + // System scheduling contention scope. + SYSTEM = PTHREAD_SCOPE_SYSTEM, + // Process scheduling contention scope. + PROCESS = PTHREAD_SCOPE_PROCESS, +} + +Cancel_State :: enum c.int { + ENABLE = PTHREAD_CANCEL_ENABLE, + DISABLE = PTHREAD_CANCEL_DISABLE, +} + +Cancel_Type :: enum c.int { + DEFERRED = PTHREAD_CANCEL_DEFERRED, + ASYNCHRONOUS = PTHREAD_CANCEL_ASYNCHRONOUS, +} + +when ODIN_OS == .Darwin { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 0x00 + PTHREAD_CANCEL_DEFERRED :: 0x02 + + PTHREAD_CANCEL_DISABLE :: 0x00 + PTHREAD_CANCEL_ENABLE :: 0x01 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 2 + PTHREAD_CREATE_JOINABLE :: 1 + + PTHREAD_EXPLICIT_SCHED :: 2 + PTHREAD_INHERIT_SCHED :: 1 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 2 + + PTHREAD_SCOPE_PROCESS :: 2 + PTHREAD_SCOPE_SYSTEM :: 1 + + pthread_t :: distinct u64 + + pthread_attr_t :: struct { + __sig: c.long, + __opaque: [56]c.char, + } + + pthread_key_t :: distinct c.ulong + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + _: [4]c.char, + } + +} else when ODIN_OS == .FreeBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 0x02 + PTHREAD_CANCEL_DEFERRED :: 0x00 + + PTHREAD_CANCEL_DISABLE :: 0x01 + PTHREAD_CANCEL_ENABLE :: 0x00 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 0 + PTHREAD_INHERIT_SCHED :: 4 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 0 + PTHREAD_PROCESS_PRIVATE :: 1 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 2 + + pthread_t :: distinct u64 + + pthread_attr_t :: distinct rawptr + + pthread_key_t :: distinct c.int + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else when ODIN_OS == .NetBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + PTHREAD_CANCEL_DEFERRED :: 0 + + PTHREAD_CANCEL_DISABLE :: 1 + PTHREAD_CANCEL_ENABLE :: 0 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 1 + PTHREAD_INHERIT_SCHED :: 0 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 0 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 1 + + pthread_t :: distinct rawptr + + pthread_attr_t :: struct { + pta_magic: c.uint, + pta_flags: c.int, + pta_private: rawptr, + } + + pthread_key_t :: distinct c.int + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else when ODIN_OS == .OpenBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 2 + PTHREAD_CANCEL_DEFERRED :: 0 + + PTHREAD_CANCEL_DISABLE :: 1 + PTHREAD_CANCEL_ENABLE :: 0 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 0x1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 0 + PTHREAD_INHERIT_SCHED :: 0x4 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 0 + PTHREAD_PROCESS_PRIVATE :: 1 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 0x2 + + pthread_t :: distinct rawptr + pthread_attr_t :: distinct rawptr + pthread_key_t :: distinct c.int + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/pwd.odin b/core/sys/posix/pwd.odin new file mode 100644 index 000000000..546d58309 --- /dev/null +++ b/core/sys/posix/pwd.odin @@ -0,0 +1,168 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// pwd.h - password structure + +foreign lib { + /* + Rewinds the user database so that the next getpwent() returns the first entry. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + setpwent :: proc() --- + + /* + Returns the current entry in the user database. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + Example: + posix.setpwent() + defer posix.endpwent() + for e := posix.getpwent(); e != nil; e = posix.getpwent() { + fmt.println(e) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + @(link_name=LGETPWENT) + getpwent :: proc() -> ^passwd --- + + /* + Closes the user database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + endpwent :: proc() --- + + /* + Searches the database for an entry with a matching name. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html ]] + */ + @(link_name=LGETPWNAM) + getpwnam :: proc(name: cstring) -> ^passwd --- + + /* + Searches the database for an entry with a matching name. + Populating the pwd fields and using the buffer to allocate strings into. + Setting result to nil on failure and to the address of pwd otherwise. + + ERANGE will be returned if there is not enough space in buffer. + sysconf(_SC_GETPW_R_SIZE_MAX) can be called for the suggested size of this buffer, note that it could return -1. + + Example: + length := posix.sysconf(._GETPW_R_SIZE_MAX) + length = length == -1 ? 1024 : length + + buffer: [dynamic]byte + defer delete(buffer) + + result: posix.passwd + resultp: ^posix.passwd + errno: posix.Errno + for { + if err := resize(&buffer, length); err != nil { + fmt.panicf("allocation failure: %v", err) + } + + errno = posix.getpwnam_r("root", &result, raw_data(buffer), len(buffer), &resultp) + if errno != .ERANGE { + break + } + } + + if errno != .NONE { + panic(string(posix.strerror(errno))) + } + + fmt.println(result) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html ]] + */ + @(link_name=LGETPWNAMR) + getpwnam_r :: proc(name: cstring, pwd: ^passwd, buffer: [^]byte, bufsize: c.size_t, result: ^^passwd) -> Errno --- + + /* + Searches the database for an entry with a matching uid. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid.html ]] + */ + @(link_name=LGETPWUID) + getpwuid :: proc(uid: uid_t) -> ^passwd --- + + /* + Searches the database for an entry with a matching uid. + Populating the pwd fields and using the buffer to allocate strings into. + Setting result to nil on failure and to the address of pwd otherwise. + + ERANGE will be returned if there is not enough space in buffer. + sysconf(_SC_GETPW_R_SIZE_MAX) can be called for the suggested size of this buffer, note that it could return -1. + + See the example for getpwnam_r. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html ]] + */ + @(link_name=LGETPWUIDR) + getpwuid_r :: proc(uid: uid_t, pwd: ^passwd, buffer: [^]byte, bufsize: c.size_t, result: ^^passwd) -> Errno --- +} + +when ODIN_OS == .NetBSD { + @(private) LGETPWENT :: "__getpwent50" + @(private) LGETPWNAM :: "__getpwnam50" + @(private) LGETPWNAMR :: "__getpwnam_r50" + @(private) LGETPWUID :: "__getpwuid50" + @(private) LGETPWUIDR :: "__getpwuid_r50" +} else { + @(private) LGETPWENT :: "getpwent" + @(private) LGETPWNAM :: "getpwnam" + @(private) LGETPWNAMR :: "getpwnam_r" + @(private) LGETPWUID :: "getpwuid" + @(private) LGETPWUIDR :: "getpwuid_r" +} + +when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_change: time_t, /* password change time */ + pw_class: cstring, /* user access class */ + pw_gecos: cstring, /* Honeywell login info */ + pw_dir: cstring, /* [PSX] home directory */ + pw_shell: cstring, /* [PSX] default shell */ + pw_expire: time_t, /* account expiration */ + } + +} else when ODIN_OS == .FreeBSD { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_change: time_t, /* password change time */ + pw_class: cstring, /* user access class */ + pw_gecos: cstring, /* Honeywell login info */ + pw_dir: cstring, /* [PSX] home directory */ + pw_shell: cstring, /* [PSX] default shell */ + pw_expire: time_t, /* account expiration */ + pw_fields: c.int, + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sched.odin b/core/sys/posix/sched.odin new file mode 100644 index 000000000..6623ba6e6 --- /dev/null +++ b/core/sys/posix/sched.odin @@ -0,0 +1,105 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sched.h - execution scheduling + +foreign lib { + /* + Returns the minimum for the given scheduling policy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_max.html ]] + */ + sched_get_priority_max :: proc(policy: Sched_Policy) -> c.int --- + + /* + Returns the maximum for the given scheduling policy. + + Returns: -1 (setting errno) on failure, the maximum on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_max.html ]] + */ + sched_get_priority_min :: proc(policy: Sched_Policy) -> c.int --- + + /* + Forces the running thread to relinquish the processor until it again becomes the head of its thread list. + */ + sched_yield :: proc() -> result --- + + /* NOTE: unimplemented on darwin (I think?). + /* + Get the scheduling params of a process, pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_getparam.html ]] + */ + sched_getparam :: proc(pid: pid_t, param: ^sched_param) -> result --- + /* + Sets the scheduling parameters of the given process, pid of 0 will set that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_setparam.html ]] + */ + sched_setparam :: proc(pid: pid_t, param: ^sched_param) -> result --- + + /* + Returns the scheduling policy of a process, pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_getscheduler.html ]] + */ + sched_getscheduler :: proc(pid: pid_t) -> Sched_Policy --- + + /* + Sets the scheduling policy and parameters of the process, pid 0 will be the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_setscheduler.html ]] + */ + sched_setscheduler :: proc(pid: pid_t, policy: Sched_Policy, param: ^sched_param) -> result --- + + /* + Updates the timespec structure to contain the current execution time limit for the process. + pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_rr_get_interval.html ]] + */ + sched_rr_get_interval :: proc(pid: pid_t, interval: ^timespec) -> result --- + */ +} + +Sched_Policy :: enum c.int { + // Error condition of sched_getscheduler. + ERROR = -1, + // First in-first out (FIFO) scheduling policy. + FIFO = SCHED_FIFO, + // Round robin scheduling policy. + RR = SCHED_RR, + // Another scheduling policy. + OTHER = SCHED_OTHER, +} + +when ODIN_OS == .Darwin { + + SCHED_FIFO :: 4 + SCHED_RR :: 2 + // SCHED_SPORADIC :: 3 NOTE: not a thing on freebsd, netbsd and probably others, leaving it out + SCHED_OTHER :: 1 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + SCHED_FIFO :: 1 + SCHED_RR :: 3 + SCHED_OTHER :: 2 + +} else when ODIN_OS == .NetBSD { + + SCHED_OTHER :: 0 + SCHED_FIFO :: 1 + SCHED_RR :: 2 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/setjmp.odin b/core/sys/posix/setjmp.odin new file mode 100644 index 000000000..cb1dad184 --- /dev/null +++ b/core/sys/posix/setjmp.odin @@ -0,0 +1,58 @@ +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// setjmp.h - stack environment declarations + +foreign lib { + /* + Equivalent to longjmp() but must not touch signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_longjmp.html ]] + */ + _longjmp :: proc(env: ^jmp_buf, val: c.int) -> ! --- + + /* + Equivalent to setjmp() but must not touch signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_longjmp.html ]] + */ + _setjmp :: proc(env: ^jmp_buf) -> c.int --- + + /* + Equivalent to longjmp() but restores saved signal masks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siglongjump.html ]] + */ + @(link_name=LSIGLONGJMP) + siglongjmp :: proc(env: ^sigjmp_buf, val: c.int) -> ! --- + + /* + Equivalent to setjmp() but restores saved signal masks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siglongjump.html ]] + */ + @(link_name=LSIGSETJMP) + sigsetjmp :: proc(env: ^sigjmp_buf, savemask: b32) -> c.int --- +} + +jmp_buf :: libc.jmp_buf +sigjmp_buf :: distinct jmp_buf + +longjmp :: libc.longjmp +setjmp :: libc.setjmp + +when ODIN_OS == .NetBSD { + @(private) LSIGSETJMP :: "__sigsetjmp14" + @(private) LSIGLONGJMP :: "__siglongjmp14" +} else { + @(private) LSIGSETJMP :: "sigsetjmp" + @(private) LSIGLONGJMP :: "siglongjmp" +} diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin new file mode 100644 index 000000000..c35494185 --- /dev/null +++ b/core/sys/posix/signal.odin @@ -0,0 +1,1131 @@ +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// signal.h - signals + +foreign lib { + // LIBC: + + /* + Set a signal handler. + + func can either be: + - `auto_cast posix.SIG_DFL` setting the default handler for that specific signal + - `auto_cast posix.SIG_IGN` causing the specific signal to be ignored + - a custom signal handler + + Returns: SIG_ERR (setting errno), the last value of func on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html ]] + */ + signal :: proc(sig: Signal, func: proc "c" (Signal)) -> proc "c" (Signal) --- + + /* + Raises a signal, calling its handler and then returning. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html ]] + */ + raise :: proc(sig: Signal) -> result --- + + // POSIX: + + /* + Raise a signal to the process/group specified by pid. + + If sig is 0, this function can be used to check if the pid is just checked for validity. + + If pid is -1, the signal is sent to all processes that the current process has permission to send. + + If pid is negative (not -1), the signal is sent to all processes in the group identifier by the + absolute value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html ]] + */ + kill :: proc(pid: pid_t, sig: Signal) -> result --- + + /* + Shorthand for `kill(-pgrp, sig)` which will kill all processes in the given process group. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/killpg.html ]] + */ + killpg :: proc(pgrp: pid_t, sig: Signal) -> result --- + + /* + Writes a language-dependent message to stderror. + + Example: + posix.psignal(.SIGSEGV, "that didn't go well") + + Possible Output: + that didn't go well: Segmentation fault + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/psignal.html ]] + */ + psignal :: proc(signum: Signal, message: cstring) --- + + /* + Send a signal to a thread. + + As with kill, if sig is 0, only validation (of the pthread_t given) is done and no signal is sent. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html ]] + */ + pthread_kill :: proc(thread: pthread_t, sig: Signal) -> Errno --- + + /* + Examine and change blocked signals. + + Equivalent to sigprocmask(), without the restriction that the call be made in a single-threaded process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html ]] + */ + pthread_sigmask :: proc(how: Sig, set: ^sigset_t, oset: ^sigset_t) -> Errno --- + + /* + Examine and change blocked signals in a single-threaded process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html ]] + */ + @(link_name=LSIGPROCMASK) + sigprocmask :: proc(how: Sig, set: ^sigset_t, oldset: ^sigset_t) -> result --- + + /* + Examine and change a signal action. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaction.html ]] + */ + @(link_name=LSIGACTION) + sigaction :: proc(sig: Signal, act: ^sigaction_t, oact: ^sigaction_t) -> result --- + + @(link_name=LSIGADDSET) + sigaddset :: proc(set: ^sigset_t, signo: Signal) -> result --- + @(link_name=LSIGDELSET) + sigdelset :: proc(^sigset_t, Signal) -> c.int --- + @(link_name=LSIGEMPTYSET) + sigemptyset :: proc(^sigset_t) -> c.int --- + @(link_name=LSIGFILLSET) + sigfillset :: proc(^sigset_t) -> c.int --- + + /* + Set and get the signal alternate stack context. + + Example: + sigstk := posix.stack_t { + ss_sp = make([^]byte, posix.SIGSTKSZ) or_else panic("allocation failure"), + ss_size = posix.SIGSTKSZ, + ss_flags = {}, + } + if posix.sigaltstack(&sigstk, nil) != .OK { + fmt.panicf("sigaltstack failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaltstack.html ]] + */ + @(link_name=LSIGALTSTACK) + sigaltstack :: proc(ss: ^stack_t, oss: ^stack_t) -> result --- + + /* + Adds sig to the signal mask of the calling process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sighold :: proc(sig: Signal) -> result --- + + /* + Sets the disposition of sig to SIG_IGN. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigignore :: proc(sig: Signal) -> result --- + + /* + Removes sig from the signal mask of the calling process and suspend the calling process until + a signal is received. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigpause :: proc(sig: Signal) -> result --- + + /* + Removes sig from the signal mask of the calling process. + + Returns: always -1. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigrelse :: proc(sig: Signal) -> result --- + + /* + Changes the restart behavior when a function is interrupted by the specified signal. + + If flag is true, SA_RESTART is removed, added otherwise. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siginterrupt.html ]] + */ + siginterrupt :: proc(sig: Signal, flag: b32) -> result --- + + /* + Test for a signal in a signal set. + + Returns: 1 if it is a member, 0 if not, -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigismember.html ]] + */ + @(link_name=LSIGISMEMBER) + sigismember :: proc(set: ^sigset_t, signo: Signal) -> c.int --- + + /* + Stores the set of signals that are blocked from delivery to the calling thread and that are pending + on the process or the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigpending.html ]] + */ + @(link_name=LSIGPENDING) + sigpending :: proc(set: ^sigset_t) -> result --- + + /* + Wait for one of the given signals. + + Returns: always -1 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigsuspend.html ]] + */ + @(link_name=LSIGSUSPEND) + sigsuspend :: proc(sigmask: ^sigset_t) -> result --- + + /* + Wait for queued signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigwait.html ]] + */ + sigwait :: proc(set: ^sigset_t, sig: ^Signal) -> Errno --- + + /* NOTE: unimplemented on darwin. + + void psiginfo(const siginfo_t *, const char *); + int sigqueue(pid_t, int, union sigval); + void (*sigset(int, void (*)(int)))(int); + int sigsuspend(const sigset_t *); + int sigtimedwait(const sigset_t *restrict, siginfo_t *restrict, + const struct timespec *restrict); + int sigwaitinfo(const sigset_t *restrict, siginfo_t *restrict); + */ +} + +sigval :: struct #raw_union { + sigval_int: c.int, /* [PSX] integer signal value */ + sigval_ptr: rawptr, /* [PSX] pointer signal value */ +} + +Signal :: enum c.int { + NONE, + + // LIBC: + + // Process abort signal. + SIGABRT = SIGABRT, + // Erronous arithemtic operation. + SIGFPE = SIGFPE, + // Illegal instruction. + SIGILL = SIGILL, + // Terminal interrupt signal. + SIGINT = SIGINT, + // Invalid memory reference. + SIGSEGV = SIGSEGV, + // Termination signal. + SIGTERM = SIGTERM, + + // POSIX: + + // Process abort signal. + SIGALRM = SIGALRM, + // Access to an undefined portion of a memory object. + SIGBUS = SIGBUS, + // Child process terminated, stopped, or continued. + SIGCHLD = SIGCHLD, + // Continue execution, if stopped. + SIGCONT = SIGCONT, + // Hangup. + SIGHUP = SIGHUP, + // Kill (cannot be caught or ignored). + SIGKILL = SIGKILL, + // Write on a pipe with no one to read it. + SIGPIPE = SIGPIPE, + // Terminal quit signal. + SIGQUIT = SIGQUIT, + // Stop executing (cannot be caught or ignored). + SIGSTOP = SIGSTOP, + // Terminal stop process. + SIGTSTP = SIGTSTP, + // Background process attempting read. + SIGTTIN = SIGTTIN, + // Background process attempting write. + SIGTTOU = SIGTTOU, + // User-defined signal 1. + SIGUSR1 = SIGUSR1, + // User-defined signal 2. + SIGUSR2 = SIGUSR2, + // Pollable event. + SIGPOLL = SIGPOLL, + // Profiling timer expired. + SIGPROF = SIGPROF, + // Bad system call. + SIGSYS = SIGSYS, + // Trace/breakpoint trap. + SIGTRAP = SIGTRAP, + // High bandwidth data is available at a socket. + SIGURG = SIGURG, + // Virtual timer expired. + SIGVTALRM = SIGVTALRM, + // CPU time limit exceeded. + SIGXCPU = SIGXCPU, + // File size limit exceeded. + SIGXFSZ = SIGXFSZ, +} + +ILL_Code :: enum c.int { + // Illegal opcode. + ILLOPC = ILL_ILLOPC, + // Illegal operand. + ILLOPN = ILL_ILLOPN, + // Illegal addressing mode. + ILLADR = ILL_ILLADR, + // Illegal trap. + ILLTRP = ILL_ILLTRP, + // Priviledged opcode. + PRVOPC = ILL_PRVOPC, + // Priviledged register. + PRVREG = ILL_PRVREG, + // Coprocessor error. + COPROC = ILL_COPROC, + // Internal stack error. + BADSTK = ILL_BADSTK, +} + +FPE_Code :: enum c.int { + // Integer divide by zero. + INTDIV = FPE_INTDIV, + // Integer overflow. + INTOVF = FPE_INTOVF, + // Floating-point divide by zero. + FLTDIV = FPE_FLTDIV, + // Floating-point overflow. + FLTOVF = FPE_FLTOVF, + // Floating-point underflow. + FLTUND = FPE_FLTUND, + // Floating-point inexact result. + FLTRES = FPE_FLTRES, + // Invalid floating-point operation. + FLTINV = FPE_FLTINV, + // Subscript out of range. + FLTSUB = FPE_FLTSUB, +} + +SEGV_Code :: enum c.int { + // Address not mapped to object. + MAPERR = SEGV_MAPERR, + // Invalid permissions for mapped object. + ACCERR = SEGV_ACCERR, +} + +BUS_Code :: enum c.int { + // Invalid address alignment. + ADRALN = BUS_ADRALN, + // Nonexistent physical address. + ADRERR = BUS_ADRERR, + // Object-specific hardware error. + OBJERR = BUS_OBJERR, +} + +TRAP_Code :: enum c.int { + // Process breakpoint. + BRKPT = TRAP_BRKPT, + // Process trace trap. + TRACE = TRAP_TRACE, +} + +CLD_Code :: enum c.int { + // Child has exited.. + EXITED = CLD_EXITED, + // Child has terminated abnormally and did not create a core file. + KILLED = CLD_KILLED, + // Child has terminated abnormally and created a core file. + DUMPED = CLD_DUMPED, + // Traced child trapped. + TRAPPED = CLD_TRAPPED, + // Child has stopped. + STOPPED = CLD_STOPPED, + // Stopped child has continued. + CONTINUED = CLD_CONTINUED, +} + +POLL_Code :: enum c.int { + // Data input is available. + IN = POLL_IN, + // Output buffers available. + OUT = POLL_OUT, + // Input message available. + MSG = POLL_MSG, + // I/O error. + ERR = POLL_ERR, + // High priority input available. + PRI = POLL_PRI, + // Device disconnected. + HUP = POLL_HUP, +} + +Any_Code :: enum c.int { + // Signal sent by kill(). + USER = SI_USER, + // Signal sent by sigqueue(). + QUEUE = SI_QUEUE, + // Signal generated by expiration of a timer set by timer_settime(). + TIMER = SI_TIMER, + // Signal generated by completion of an asynchronous I/O request. + ASYNCIO = SI_ASYNCIO, + // Signal generated by arrival of a message on an empty message queue. + MESGQ = SI_MESGQ, +} + +SA_Flags_Bits :: enum c.int { + // Do not generate SIGCHLD when children stop or stopped children continue. + NOCLDSTOP = log2(SA_NOCLDSTOP), + // Cause signal delivery to occur on an alternate stack. + ONSTACK = log2(SA_ONSTACK), + // Cause signal disposition to be set to SIG_DFL on entry to signal handlers. + RESETHAND = log2(SA_RESETHAND), + // Cause certain functions to become restartable. + RESTART = log2(SA_RESTART), + // Cause extra information to be passed to signal handlers at the time of receipt of a signal. + SIGINFO = log2(SA_SIGINFO), + // Cause implementation not to create zombie processes or status information on child termination. + NOCLDWAIT = log2(SA_NOCLDWAIT), + // Cause signal not to be automatically blocked on entry to signal handler. + SA_NODEFER = log2(SA_NODEFER), +} +SA_Flags :: bit_set[SA_Flags_Bits; c.int] + +SS_Flag_Bits :: enum c.int { + // Process is executing on an alternate signal stack. + ONSTACK = log2(SS_ONSTACK), + // Alternate signal stack is disabled. + DISABLE = log2(SS_DISABLE), +} +SS_Flags :: bit_set[SS_Flag_Bits; c.int] + +Sig :: enum c.int { + // Resulting set is the union of the current set and the signal set and the complement of + // the signal set pointed to by the argument. + BLOCK = SIG_BLOCK, + // Resulting set is the intersection of the current set and the complement of the signal set + // pointed to by the argument. + UNBLOCK = SIG_UNBLOCK, + // Resulting set is the signal set pointed to by the argument. + SETMASK = SIG_SETMASK, +} + +// Request for default signal handling. +SIG_DFL :: libc.SIG_DFL +// Return value from signal() in case of error. +SIG_ERR :: libc.SIG_ERR +// Request that signal be ignored. +SIG_IGN :: libc.SIG_IGN + +SIGABRT :: libc.SIGABRT +SIGFPE :: libc.SIGFPE +SIGILL :: libc.SIGILL +SIGINT :: libc.SIGINT +SIGSEGV :: libc.SIGSEGV +SIGTERM :: libc.SIGTERM + +when ODIN_OS == .NetBSD { + @(private) LSIGPROCMASK :: "__sigprocmask14" + @(private) LSIGACTION :: "__sigaction_siginfo" + @(private) LSIGADDSET :: "__sigaddset14" + @(private) LSIGDELSET :: "__sigdelset14" + @(private) LSIGEMPTYSET :: "__sigemptyset14" + @(private) LSIGFILLSET :: "__sigfillset14" + @(private) LSIGALTSTACK :: "__sigaltstack14" + @(private) LSIGISMEMBER :: "__sigismember14" + @(private) LSIGPENDING :: "__sigpending14" + @(private) LSIGSUSPEND :: "__sigsuspend14" +} else { + @(private) LSIGPROCMASK :: "sigprocmask" + @(private) LSIGACTION :: "sigaction" + @(private) LSIGADDSET :: "sigaddset" + @(private) LSIGDELSET :: "sigdelset" + @(private) LSIGEMPTYSET :: "sigemptyset" + @(private) LSIGFILLSET :: "sigfillset" + @(private) LSIGALTSTACK :: "sigaltstack" + @(private) LSIGISMEMBER :: "sigismember" + @(private) LSIGPENDING :: "sigpending" + @(private) LSIGSUSPEND :: "sigsuspend" +} + +when ODIN_OS == .Darwin { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(5)) + + uid_t :: distinct c.uint32_t + sigset_t :: distinct c.uint32_t + + // MOTE: unimplemented on darwin. + // + // SIGRTMIN :: + // SIGRTMAX :: + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 32768 + SIGSTKSZ :: 131072 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value or signal */ + si_addr: rawptr, /* [PSX] address of faulting instruction */ + si_value: sigval, /* [PSX] signal value */ + si_band: c.long, /* [PSX] band event for SIGPOLL */ + __pad: [7]c.ulong, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 4 + ILL_ILLADR :: 5 + ILL_ILLTRP :: 2 + ILL_PRVOPC :: 3 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 7 + FPE_INTOVF :: 8 + FPE_FLTDIV :: 1 + FPE_FLTOVF :: 2 + FPE_FLTUND :: 3 + FPE_FLTRES :: 4 + FPE_FLTINV :: 5 + FPE_FLTSUB :: 6 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0x10001 + SI_QUEUE :: 0x10002 + SI_TIMER :: 0x10003 + SI_ASYNCIO :: 0x10004 + SI_MESGQ :: 0x10005 + +} else when ODIN_OS == .FreeBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + + sigset_t :: struct { + __bits: [4]c.uint32_t, + } + + // MOTE: unimplemented on darwin. + // + // SIGRTMIN :: 65 + // SIGRTMAX :: 126 + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_flags: SA_Flags, /* [PSX] special flags */ + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm32 { + MINSIGSTKSZ :: 1024 * 4 + } else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + MINSIGSTKSZ :: 512 * 4 + } + + SIGSTKSZ :: MINSIGSTKSZ + 32768 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value or signal */ + si_addr: rawptr, /* [PSX] address of faulting instruction */ + si_value: sigval, /* [PSX] signal value */ + using _reason: struct #raw_union { + _fault: struct { + _trapno: c.int, /* machine specific trap code */ + }, + _timer: struct { + _timerid: c.int, + _overrun: c.int, + }, + _mesgq: struct { + _mqd: c.int, + }, + using _poll: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + _capsicum: struct { + _syscall: c.int, /* syscall number for signals delivered as a result of system calls denied by capsicum */ + }, + __spare__: struct { + __spare1__: c.long, + __spare2__: [7]c.int, + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 2 + FPE_INTOVF :: 1 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0x10001 + SI_QUEUE :: 0x10002 + SI_TIMER :: 0x10003 + SI_ASYNCIO :: 0x10004 + SI_MESGQ :: 0x10005 + +} else when ODIN_OS == .NetBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + sigset_t :: struct { + __bits: [4]c.uint32_t, + } + + // MOTE: unimplemented on darwin. + // + // SIGRTMIN :: 33 + // SIGRTMAX :: 63 + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 8192 + SIGSTKSZ :: MINSIGSTKSZ + 32768 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + @(private) + lwpid_t :: c.int32_t + + siginfo_t :: struct #raw_union { + si_pad: [128]byte, + using _info: struct { + si_signo: Signal, /* [PSX] signal number */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_errno: Errno, /* [PSX] errno value associated with this signal */ + // #ifdef _LP64 + /* In _LP64 the union starts on an 8-byte boundary. */ + _pad: c.int, + // #endif + using _reason: struct #raw_union { + using _rt: struct { + _pid: pid_t, + _uid: uid_t, + si_value: sigval, /* [PSX] signal value */ + }, + using _child: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value or signal */ + _utime: clock_t, + _stime: clock_t, + }, + using _fault: struct { + si_addr: rawptr, /* [PSX] address of faulting instruction */ + _trap: c.int, + _trap2: c.int, + _trap3: c.int, + }, + using _poll: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + _fd: FD, + }, + _syscall: struct { + _sysnum: c.int, + _retval: [2]c.int, + _error: c.int, + _args: [8]c.uint64_t, + }, + _ptrace_state: struct { + _pe_report_event: c.int, + _option: struct #raw_union { + _pe_other_pid: pid_t, + _pe_lwp: lwpid_t, + }, + }, + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -1 + SI_TIMER :: -2 + SI_ASYNCIO :: -3 + SI_MESGQ :: -4 + +} else when ODIN_OS == .OpenBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + sigset_t :: distinct c.uint32_t + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 3 << 12 + SIGSTKSZ :: MINSIGSTKSZ + (1 << 12) * 4 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + SI_MAXSZ :: 128 + SI_PAD :: (SI_MAXSZ / size_of(c.int)) - 3 + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_errno: Errno, /* [PSX] errno value associated with this signal */ + using _data: struct #raw_union { + _pad: [SI_PAD]c.int, + using _proc: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + using _pdata: struct #raw_union { + using _kill: struct { + si_value: sigval, + }, + using _cld: struct { + _utime: clock_t, + _stime: clock_t, + si_status: c.int, + }, + }, + }, + using _fault: struct { + si_addr: rawptr, + _trapno: c.int, + }, + using _file: struct { + _fd: FD, + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -2 + SI_TIMER :: -3 + SI_ASYNCIO :: -4 // NOTE: not implemented + SI_MESGQ :: -5 // NOTE: not implemented + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/stdio.odin b/core/sys/posix/stdio.odin new file mode 100644 index 000000000..de716f8d7 --- /dev/null +++ b/core/sys/posix/stdio.odin @@ -0,0 +1,332 @@ +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdio.h - standard buffered input/output + +foreign lib { + /* + Generates a string that, when used as a pathname, + refers to the current controlling terminal for the current process. + + If s is nil, the returned string might be static and overwritten by subsequent calls or other factors. + If s is not nil, s is assumed len(s) >= L_ctermid. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ctermid.html ]] + */ + ctermid :: proc(s: [^]byte) -> cstring --- + + /* + Equivalent to fprintf but output is written to the file descriptor. + + Return: number of bytes written, negative (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] + */ + dprintf :: proc(fildse: FD, format: cstring, #c_vararg args: ..any) -> c.int --- + + /* + Equivalent to fprintf but output is written to s, it is the user's responsibility to + ensure there is enough space. + + Return: number of bytes written, negative (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] + */ + sprintf :: proc(s: [^]byte, format: cstring, #c_vararg args: ..any) -> c.int --- + + /* + Associate a stream with a file descriptor. + + Returns: nil (setting errno) on failure, the stream on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopen.html ]] + */ + fdopen :: proc(fildes: FD, mode: cstring) -> ^FILE --- + + /* + Map a stream pointer to a file descriptor. + + Returns: the file descriptor or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fileno.html ]] + */ + fileno :: proc(stream: ^FILE) -> FD --- + + /* + Locks a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + flockfile :: proc(file: ^FILE) --- + + /* + Tries to lock a file. + + Returns: 0 if it could be locked, non-zero if it couldn't + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + ftrylockfile :: proc(file: ^FILE) -> c.int --- + + /* + Unlocks a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + funlockfile :: proc(file: ^FILE) --- + + /* + Open a memory buffer stream. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html ]] + */ + fmemopen :: proc(buf: [^]byte, size: c.size_t, mode: cstring) -> ^FILE --- + + /* + Reposition a file-position indicator in a stream. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseeko.html ]] + */ + fseeko :: proc(stream: ^FILE, offset: off_t, whence: Whence) -> result --- + + /* + Return the file offset in a stream. + + Returns: the current file offset, -1 (setting errno) on error + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftello.html ]] + */ + ftello :: proc(^FILE) -> off_t --- + + /* + Open a dynamic memory buffer stream. + + Returns: nil (setting errno) on failure, the stream on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open_memstream.html ]] + */ + open_memstream :: proc(bufp: ^[^]byte, sizep: ^c.size_t) -> ^FILE --- + + /* + Equivalent to getc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + getc_unlocked :: proc(stream: ^FILE) -> c.int --- + + /* + Equivalent to getchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + getchar_unlocked :: proc() -> c.int --- + + /* + Equivalent to putc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + putc_unlocked :: proc(ch: c.int, stream: ^FILE) -> c.int --- + + /* + Equivalent to putchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + putchar_unlocked :: proc(ch: c.int) -> c.int --- + + /* + Read a delimited record from the stream. + + Returns: the number of bytes written or -1 on failure/EOF + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html ]] + */ + getdelim :: proc(lineptr: ^cstring, n: ^c.size_t, delimiter: c.int, stream: ^FILE) -> c.ssize_t --- + + /* + Read a line delimited record from the stream. + + Returns: the number of bytes written or -1 on failure/EOF + + Example: + fp := posix.fopen(#file, "r") + if fp == nil { + posix.exit(1) + } + + line: cstring + length: uint + for { + read := posix.getline(&line, &length, fp) + if read == -1 do break + posix.printf("Retrieved line of length %zu :\n", read) + posix.printf("%s", line) + } + if posix.ferror(fp) != 0 { + /* handle error */ + } + posix.free(rawptr(line)) + posix.fclose(fp) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html ]] + */ + getline :: proc(lineptr: ^cstring, n: ^c.size_t, stream: ^FILE) -> c.ssize_t --- + + /* + Get a string from the stdin stream. + + It is up to the user to make sure s is big enough. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gets.html ]] + */ + gets :: proc(s: [^]byte) -> cstring --- + + /* + Create a name for a temporary file. + + Returns: an allocated cstring that needs to be freed, nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html ]] + */ + tempnam :: proc(dir: cstring, pfx: cstring) -> cstring --- + + /* + Executes the command specified, creating a pipe and returning a pointer to a stream that can + read or write from/to the pipe. + + Returns: nil (setting errno) on failure or a pointer to the stream + + Example: + fp := posix.popen("ls *", "r") + if fp == nil { + /* Handle error */ + } + + path: [1024]byte + for posix.fgets(raw_data(path[:]), len(path), fp) != nil { + posix.printf("%s", &path) + } + + status := posix.pclose(fp) + if status == -1 { + /* Error reported by pclose() */ + } else { + /* Use functions described under wait() to inspect `status` in order + to determine success/failure of the command executed by popen() */ + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/popen.html ]] + */ + popen :: proc(command: cstring, mode: cstring) -> ^FILE --- + + /* + Closes a pipe stream to or from a process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pclose.html ]] + */ + pclose :: proc(stream: ^FILE) -> c.int --- + + /* + Equivalent to rename but relative directories are resolved from their respective fds. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html ]] + */ + renameat :: proc(oldfd: FD, old: cstring, newfd: FD, new: cstring) -> result --- +} + +clearerr :: libc.clearerr +fclose :: libc.fclose +feof :: libc.feof +ferror :: libc.ferror +fflush :: libc.fflush +fgetc :: libc.fgetc +fgetpos :: libc.fgetpos +fgets :: libc.fgets +fopen :: libc.fopen +fprintf :: libc.fprintf +fputc :: libc.fputc +fread :: libc.fread +freopen :: libc.freopen +fscanf :: libc.fscanf +fseek :: libc.fseek +fsetpos :: libc.fsetpos +ftell :: libc.ftell +fwrite :: libc.fwrite +getc :: libc.getc +getchar :: libc.getchar +perror :: libc.perror +printf :: libc.printf +putc :: libc.puts +putchar :: libc.putchar +puts :: libc.puts +remove :: libc.remove +rename :: libc.rename +rewind :: libc.rewind +scanf :: libc.scanf +setbuf :: libc.setbuf +setvbuf :: libc.setvbuf +snprintf :: libc.snprintf +sscanf :: libc.sscanf +tmpfile :: libc.tmpfile +tmpnam :: libc.tmpnam +vfprintf :: libc.vfprintf +vfscanf :: libc.vfscanf +vprintf :: libc.vprintf +vscanf :: libc.vscanf +vsnprintf :: libc.vsnprintf +vsprintf :: libc.vsprintf +vsscanf :: libc.vsscanf +ungetc :: libc.ungetc + +to_stream :: libc.to_stream + +Whence :: libc.Whence +FILE :: libc.FILE +fpos_t :: libc.fpos_t + +BUFSIZ :: libc.BUFSIZ + +_IOFBF :: libc._IOFBF +_IOLBF :: libc._IOLBF +_IONBF :: libc._IONBF + +SEEK_CUR :: libc.SEEK_CUR +SEEK_END :: libc.SEEK_END +SEEK_SET :: libc.SEEK_SET + +FILENAME_MAX :: libc.FILENAME_MAX +FOPEN_MAX :: libc.FOPEN_MAX +TMP_MAX :: libc.TMP_MAX + +EOF :: libc.EOF + +stderr := libc.stderr +stdin := libc.stdin +stdout := libc.stdout + +when ODIN_OS == .Darwin { + + L_ctermid :: 1024 + L_tmpnam :: 1024 + + P_tmpdir :: "/var/tmp/" + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + L_ctermid :: 1024 + L_tmpnam :: 1024 + + P_tmpdir :: "/tmp/" + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/stdlib.odin b/core/sys/posix/stdlib.odin new file mode 100644 index 000000000..a1e2eab50 --- /dev/null +++ b/core/sys/posix/stdlib.odin @@ -0,0 +1,450 @@ +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdlib.h - standard library definitions + +atof :: libc.atof +atoi :: libc.atoi +atol :: libc.atol +atoll :: libc.atoll +strtod :: libc.strtod +strtof :: libc.strtof +strtol :: libc.strtol +strtoll :: libc.strtoll +strtoul :: libc.strtoul +strtoull :: libc.strtoull + +rand :: libc.rand +srand :: libc.srand + +calloc :: libc.calloc +malloc :: libc.malloc +realloc :: libc.realloc + +abort :: libc.abort +atexit :: libc.atexit +at_quick_exit :: libc.at_quick_exit +exit :: libc.exit +_Exit :: libc._Exit +getenv :: libc.getenv +quick_exit :: libc.quick_exit +system :: libc.system + +bsearch :: libc.bsearch +qsort :: libc.qsort + +abs :: libc.abs +labs :: libc.labs +llabs :: libc.llabs +div :: libc.div +ldiv :: libc.ldiv +lldiv :: libc.lldiv + +mblen :: libc.mblen +mbtowc :: libc.mbtowc +wctomb :: libc.wctomb + +mbstowcs :: libc.mbstowcs +wcstombs :: libc.wcstombs + +free :: #force_inline proc(ptr: $T) where intrinsics.type_is_pointer(T) || intrinsics.type_is_multi_pointer(T) || T == cstring { + libc.free(rawptr(ptr)) +} + +foreign lib { + /* + Takes a pointer to a radix-64 representation, in which the first digit is the least significant, + and return the corresponding long value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/a64l.html ]] + */ + a64l :: proc(s: cstring) -> c.long --- + + /* + The l64a() function shall take a long argument and return a pointer to the corresponding + radix-64 representation. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/a64l.html ]] + */ + l64a :: proc(value: c.long) -> cstring --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, double-precision, floating-point values, uniformly distributed over the interval [0.0,1.0) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + drand48 :: proc() -> c.double --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, double-precision, floating-point values, uniformly distributed over the interval [0.0,1.0) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + erand48 :: proc(xsubi: ^[3]c.ushort) -> c.double --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: return signed long integers uniformly distributed over the interval [-231,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + mrand48 :: proc() -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: return signed long integers uniformly distributed over the interval [-231,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + jrand48 :: proc(xsubi: ^[3]c.ushort) -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, long integers, uniformly distributed over the interval [0,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + lrand48 :: proc() -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, long integers, uniformly distributed over the interval [0,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + nrand48 :: proc(xsubi: ^[3]c.ushort) -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + srand48 :: proc(seedval: c.long) --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + lcong48 :: proc(param: ^[7]c.ushort) --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + seed48 :: proc(seed16v: ^[3]c.ushort) -> ^[3]c.ushort --- + + /* + Parses suboption arguments in a flag argument. + + Returns: the index of the matched token string, or -1 if no token strings were matched + + Example: + args := runtime.args__ + + Opt :: enum { + RO, + RW, + NAME, + NIL, + } + token := [Opt]cstring{ + .RO = "ro", + .RW = "rw", + .NAME = "name", + .NIL = nil, + } + + Options :: struct { + readonly, readwrite: bool, + name: cstring, + + } + opts: Options + + errfnd: bool + for { + opt := posix.getopt(i32(len(args)), raw_data(args), "o:") + if opt == -1 { + break + } + + switch opt { + case 'o': + subopt := posix.optarg + value: cstring + for subopt != "" && !errfnd { + o := posix.getsubopt(&subopt, &token[.RO], &value) + switch Opt(o) { + case .RO: opts.readonly = true + case .RW: opts.readwrite = true + case .NAME: + if value == nil { + fmt.eprintfln("missing value for suboption %s", token[.NAME]) + errfnd = true + continue + } + + opts.name = value + case .NIL: + fallthrough + case: + fmt.eprintfln("no match found for token: %s", value) + errfnd = true + } + } + if opts.readwrite && opts.readonly { + fmt.eprintfln("Only one of %s and %s can be specified", token[.RO], token[.RW]) + errfnd = true + } + case: + errfnd = true + } + } + + if errfnd || len(args) == 1 { + fmt.eprintfln("\nUsage: %s -o ", args[0]) + fmt.eprintfln("suboptions are 'ro', 'rw', and 'name='") + posix.exit(1) + } + + fmt.println(opts) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsubopt.html ]] + */ + getsubopt :: proc(optionp: ^cstring, keylistp: [^]cstring, valuep: ^cstring) -> c.int --- + + /* + Changes the mode and ownership of the slave pseudo-terminal device associated with its master pseudo-terminal counterpart. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html ]] + */ + grantpt :: proc(fildes: FD) -> result --- + + /* + Allows a state array, pointed to by the state argument, to be initialized for future use. + + Returns: the previous state array or nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + @(link_name=LINITSTATE) + initstate :: proc(seed: c.uint, state: [^]byte, size: c.size_t) -> [^]byte --- + + /* + Sets the state array of the random number generator. + + Returns: the previous state array or nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + setstate :: proc(state: [^]byte) -> [^]byte --- + + /* + Use a non-linear additive feedback random-number generator employing a default state array + size of 31 long integers to return successive pseudo-random numbers in the range from 0 to 231-1. + The period of this random-number generator is approximately 16 x (231-1). + The size of the state array determines the period of the random-number generator. + Increasing the state array size shall increase the period. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + random :: proc() -> c.long --- + + /* + Initializes the current state array using the value of seed. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + @(link_name=LSRANDOM) + srandom :: proc(seed: c.uint) --- + + /* + Creates a directory with a unique name derived from template. + The application shall ensure that the string provided in template is a pathname ending + with at least six trailing 'X' characters. + + Returns: nil (setting errno) on failure, template on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdtemp.html ]] + */ + mkdtemp :: proc(template: [^]byte) -> cstring --- + + /* + Creates a regular file with a unique name derived from template and return a file descriptor + for the file open for reading and writing. + The application shall ensure that the string provided in template is a pathname ending with + at least six trailing 'X' characters. + + Returns: -1 (setting errno) on failure, an open file descriptor on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdtemp.html ]] + */ + mkstemp :: proc(template: cstring) -> FD --- + + /* + Allocates size bytes aligned on a boundary specified by alignment, and shall return a pointer + to the allocated memory in memptr. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html ]] + */ + posix_memalign :: proc(memptr: ^[^]byte, alignment: c.size_t, size: c.size_t) -> Errno --- + + /* + Establishes a connection between a master device for a pseudo-terminal and a file descriptor. + + Returns: -1 (setting errno) on failure, an open file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html ]] + */ + posix_openpt :: proc(oflag: O_Flags) -> FD --- + + /* + Returns the name of the slave pseudo-terminal device associated with a master pseudo-terminal device. + + Returns: nil (setting errno) on failure, the name on success, which may be invalidated on subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ptsname.html ]] + */ + ptsname :: proc(fildes: FD) -> cstring --- + + /* + Unlocks the slave pseudo-terminal device associated with the master to which fildes refers. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html ]] + */ + unlockpt :: proc(fildes: FD) -> result --- + + /* + Uses the string argument to set environment variable values. + + Returns: 0 on success, non-zero (setting errno) on failure + + Example: + if posix.putenv("HOME=/usr/home") != 0 { + fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/putenv.html ]] + */ + @(link_name=LPUTENV) + putenv :: proc(string: cstring) -> c.int --- + + /* + Updates or add a variable in the environment of the calling process. + + Example: + if posix.setenv("HOME", "/usr/home") != .OK { + fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html ]] + */ + setenv :: proc(envname: cstring, envval: cstring, overwrite: b32) -> result --- + + /* + Removes an environment variable from the environment of the calling process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html ]] + */ + @(link_name=LUNSETENV) + unsetenv :: proc(name: cstring) -> result --- + + /* + Computes a sequence of pseudo-random integers in the range [0, {RAND_MAX}]. + (The value of the {RAND_MAX} macro shall be at least 32767.) + + If rand_r() is called with the same initial value for the object pointed to by seed and that object is not modified between successive returns and calls to rand_r(), the same sequence shall be generated. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rand_r.html ]] + */ + rand_r :: proc(seed: ^c.uint) -> c.int --- + + /* + Derive, from the pathname file_name, an absolute pathname that resolves to the same directory entry, + whose resolution does not involve '.', '..', or symbolic links. + + If resolved_name is not `nil` it should be larger than `PATH_MAX` and the result will use it as a backing buffer. + If resolved_name is `nil` the returned string is allocated by `malloc`. + + Returns: `nil` (setting errno) on failure, the "real path" otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html ]] + */ + realpath :: proc(file_name: cstring, resolved_name: [^]byte = nil) -> cstring --- + + /* + Provides access to an implementation-defined encoding algorithm. + The argument of setkey() is an array of length 64 bytes containing only the bytes with numerical + value of 0 and 1. + + If this string is divided into groups of 8, the low-order bit in each group is ignored; this gives a 56-bit key which is used by the algorithm. + This is the key that shall be used with the algorithm to encode a string block passed to encrypt(). + + The setkey() function shall not change the setting of errno if successful. + An application wishing to check for error situations should set errno to 0 before calling setkey(). + If errno is non-zero on return, an error has occurred. + + Example: + key: [64]byte + // set key bytes... + + posix.set_errno(.NONE) + posix.setkey(raw_data(key)) + if errno := posix.errno(); errno != .NONE { + fmt.panicf("setkey failure: %s", posix.strerror(errno)) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setkey.html ]] + */ + setkey :: proc(key: [^]byte) --- +} + +EXIT_FAILURE :: libc.EXIT_FAILURE +EXIT_SUCCESS :: libc.EXIT_SUCCESS + +RAND_MAX :: libc.RAND_MAX +MB_CUR_MAX :: libc.MB_CUR_MAX + +div_t :: libc.div_t +ldiv_t :: libc.ldiv_t +lldiv_t :: libc.lldiv_t + +when ODIN_OS == .NetBSD { + @(private) LPUTENV :: "__putenv50" + @(private) LINITSTATE :: "__initstate60" + @(private) LSRANDOM :: "__srandom60" + @(private) LUNSETENV :: "__unsetenv13" +} else { + @(private) LPUTENV :: "putenv" + @(private) LINITSTATE :: "initstate" + @(private) LSRANDOM :: "srandom" + @(private) LUNSETENV :: "unsetenv" +} diff --git a/core/sys/posix/string.odin b/core/sys/posix/string.odin new file mode 100644 index 000000000..d22f49a96 --- /dev/null +++ b/core/sys/posix/string.odin @@ -0,0 +1,47 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// string.h - string operations + +// NOTE: most of the symbols in this header are not useful in Odin and have been left out. + +foreign lib { + /* + Map the error number to a locale-dependent error message string. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html ]] + */ + @(link_name="strerror") + _strerror :: proc(errnum: Errno) -> cstring --- + + /* + Map the error number to a locale-dependent error message string and put it in the buffer. + + Returns: ERANGE if the buffer is not big enough + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror_r.html ]] + */ + strerror_r :: proc(errnum: Errno, strerrbuf: [^]byte, buflen: c.size_t) -> Errno --- + + /* + Map the signal number to an implementation-defined string. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strsignal.html ]] + */ + strsignal :: proc(sig: Signal) -> cstring --- +} + +strerror :: #force_inline proc "contextless" (errnum: Maybe(Errno) = nil) -> cstring { + return _strerror(errnum.? or_else errno()) +} diff --git a/core/sys/posix/sys_ipc.odin b/core/sys/posix/sys_ipc.odin new file mode 100644 index 000000000..f8778ee15 --- /dev/null +++ b/core/sys/posix/sys_ipc.odin @@ -0,0 +1,89 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/ipc.h = XSI interprocess communication access structure + +foreign lib { + /* + Generate an IPC key. + + Returns: -1 (setting errno) on failure, the key otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftok.html ]] + */ + ftok :: proc(path: cstring, id: c.int) -> key_t --- +} + +IPC_Cmd :: enum c.int { + RMID = IPC_RMID, + SET = IPC_SET, + STAT = IPC_STAT, +} + +IPC_Flag_Bits :: enum c.int { + CREAT = log2(IPC_CREAT), + EXCL = log2(IPC_EXCL), + NOWAIT = log2(IPC_NOWAIT), + + MSG_NOERROR = log2(MSG_NOERROR), +} +IPC_Flags :: bit_set[IPC_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + key_t :: distinct c.int32_t + + ipc_perm :: struct { + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + _seq: c.ushort, + _key: key_t, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + key_t :: distinct c.long + + ipc_perm :: struct { + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + _seq: c.ushort, + _key: key_t, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin new file mode 100644 index 000000000..217d321ac --- /dev/null +++ b/core/sys/posix/sys_mman.odin @@ -0,0 +1,229 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// mman.h - memory management declarations + +foreign lib { + /* + Establish a mapping between an address space of a process and a memory object. + + Returns: MAP_FAILED (setting errno) on failure, the address in memory otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html ]] + */ + mmap :: proc( + addr: rawptr, + len: c.size_t, + prot: Prot_Flags, + flags: Map_Flags, + fd: FD = -1, + off: off_t = 0, + ) -> rawptr --- + + /* + Unmaps pages of memory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/munmap.html ]] + */ + munmap :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Locks a range of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]] + */ + mlock :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Unlocks a range of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]] + */ + munlock :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Locks all pages of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]] + */ + mlockall :: proc(flags: Lock_Flags) -> result --- + + /* + Unlocks all pages of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]] + */ + munlockall :: proc() -> result --- + + /* + Set protection of a memory mapping. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html ]] + */ + mprotect :: proc(addr: rawptr, len: c.size_t, prot: Prot_Flags) -> result --- + + /* + Write all modified data to permanent storage locations. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msync.html ]] + */ + @(link_name=LMSYNC) + msync :: proc(addr: rawptr, len: c.size_t, flags: Sync_Flags) -> result --- + + /* + Advise the implementation of expected behavior of the application. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_madvise.html ]] + */ + posix_madvise :: proc(addr: rawptr, len: c.size_t, advice: MAdvice) -> Errno --- + + /* + Open a shared memory object. + + Returns: -1 (setting errno) on failure, an open file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html ]] + */ + shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + + /* + Removes a shared memory object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_unlink.html ]] + */ + shm_unlink :: proc(name: cstring) -> result --- +} + +#assert(_PROT_NONE == 0) +PROT_NONE :: Prot_Flags{} + +Prot_Flag_Bits :: enum c.int { + // Data can be executed. + EXEC = log2(PROT_EXEC), + // Data can be read. + READ = log2(PROT_READ), + // Data can be written. + WRITE = log2(PROT_WRITE), +} +Prot_Flags :: bit_set[Prot_Flag_Bits; c.int] + +Map_Flag_Bits :: enum c.int { + // Interpret addr exactly. + FIXED = log2(MAP_FIXED), + // Changes are private. + PRIVATE = log2(MAP_PRIVATE), + // Changes are shared. + SHARED = log2(MAP_SHARED), +} +Map_Flags :: bit_set[Map_Flag_Bits; c.int] + +Lock_Flag_Bits :: enum c.int { + // Lock all pages currently mapped into the address space of the process. + CURRENT = log2(MCL_CURRENT), + // Lock all pages that become mapped into the address space of the process in the future, + // when those mappings are established. + FUTURE = log2(MCL_FUTURE), +} +Lock_Flags :: bit_set[Lock_Flag_Bits; c.int] + +Sync_Flags_Bits :: enum c.int { + // Perform asynchronous writes. + ASYNC = log2(MS_ASYNC), + // Invalidate cached data. + INVALIDATE = log2(MS_INVALIDATE), + + // Perform synchronous writes. + // NOTE: use with `posix.MS_SYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // LOCAL = RTLD_LOCAL + // SYNC = MS_SYNC, + + _MAX = 31, +} +Sync_Flags :: bit_set[Sync_Flags_Bits; c.int] + +MAdvice :: enum c.int { + DONTNEED = POSIX_MADV_DONTNEED, + NORMAL = POSIX_MADV_NORMAL, + RANDOM = POSIX_MADV_RANDOM, + SEQUENTIAL = POSIX_MADV_SEQUENTIAL, + WILLNEED = POSIX_MADV_WILLNEED, +} + +when ODIN_OS == .NetBSD { + @(private) LMSYNC :: "__msync13" +} else { + @(private) LMSYNC :: "msync" +} + +when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + PROT_EXEC :: 0x04 + _PROT_NONE :: 0x00 + PROT_READ :: 0x01 + PROT_WRITE :: 0x02 + + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + + when ODIN_OS == .Darwin { + MS_INVALIDATE :: 0x0002 + _MS_SYNC :: 0x0010 + } else when ODIN_OS == .NetBSD { + MS_INVALIDATE :: 0x0002 + _MS_SYNC :: 0x0004 + } else when ODIN_OS == .OpenBSD { + MS_INVALIDATE :: 0x0004 + _MS_SYNC :: 0x0002 + } + MS_ASYNC :: 0x0001 + MS_SYNC :: Sync_Flags{Sync_Flags_Bits(log2(_MS_SYNC))} + + MCL_CURRENT :: 0x0001 + MCL_FUTURE :: 0x0002 + + MAP_FAILED :: rawptr(~uintptr(0)) + + POSIX_MADV_DONTNEED :: 4 + POSIX_MADV_NORMAL :: 0 + POSIX_MADV_RANDOM :: 1 + POSIX_MADV_SEQUENTIAL :: 2 + POSIX_MADV_WILLNEED :: 3 + +} else when ODIN_OS == .FreeBSD { + + PROT_EXEC :: 0x04 + _PROT_NONE :: 0x00 + PROT_READ :: 0x01 + PROT_WRITE :: 0x02 + + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + + MS_ASYNC :: 0x0001 + MS_INVALIDATE :: 0x0002 + MS_SYNC :: Sync_Flags{} + + MCL_CURRENT :: 0x0001 + MCL_FUTURE :: 0x0002 + + MAP_FAILED :: rawptr(~uintptr(0)) + + POSIX_MADV_DONTNEED :: 4 + POSIX_MADV_NORMAL :: 0 + POSIX_MADV_RANDOM :: 1 + POSIX_MADV_SEQUENTIAL :: 2 + POSIX_MADV_WILLNEED :: 3 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_msg.odin b/core/sys/posix/sys_msg.odin new file mode 100644 index 000000000..a8b86e501 --- /dev/null +++ b/core/sys/posix/sys_msg.odin @@ -0,0 +1,161 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/msg.h = XSI message queue structures + +foreign lib { + /* + Provides various operation as specified by the given cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgctl.html ]] + */ + @(link_name=LMSGCTL) + msgctl :: proc(msqid: FD, cmd: IPC_Cmd, buf: ^msqid_ds) -> result --- + + /* + Returns the message queue identifier associated with the argument key. + + Returns: -1 (setting errno) on failure, the identifier otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgget.html ]] + */ + msgget :: proc(key: key_t, msgflg: IPC_Flags) -> FD --- + + /* + Read a message from the queue. + + Returns: -1 (setting errno) on failure, the bytes received otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgrcv.html ]] + */ + msgrcv :: proc( + msgid: FD, + msgp: rawptr, + msgsz: c.size_t, + msgtyp: c.long, + msgflg: IPC_Flags, + ) -> c.ssize_t --- + + /* + Send a message on the queue. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgsnd.html ]] + */ + msgsnd :: proc(msgid: FD, msgp: rawptr, msgsz: c.size_t, msgflg: IPC_Flags) -> result --- +} + +when ODIN_OS == .NetBSD { + @(private) LMSGCTL :: "__msgctl50" +} else { + @(private) LMSGCTL :: "msgctl" +} + +when ODIN_OS == .Darwin { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + // NOTE: this is #pragma pack(4) + + msqid_ds :: struct #align(4) { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_first: c.int32_t, + msg_last: c.int32_t, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_pad1: c.int32_t, + using _: struct #align(4) { + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_pad2: c.int32_t, + using _: struct #align(4) { + msg_ctime: time_t, /* [PSX] time of last change */ + msg_pad3: c.int32_t, + msg_pad4: [4]c.int32_t, + }, + }, + } + +} else when ODIN_OS == .FreeBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + __msg_first: rawptr, + __msg_last: rawptr, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + } + +} else when ODIN_OS == .NetBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.size_t + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + + _msg_first: rawptr, + _msg_last: rawptr, + _msg_cbytes: msglen_t, + } + +} else when ODIN_OS == .OpenBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + __msg_first: rawptr, + __msg_last: rawptr, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_pad1: c.long, + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_pad2: c.long, + msg_ctime: time_t, /* [PSX] time of last change */ + msg_pad3: c.long, + msg_pad4: [4]c.long, + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_resource.odin b/core/sys/posix/sys_resource.odin new file mode 100644 index 000000000..6716d60c3 --- /dev/null +++ b/core/sys/posix/sys_resource.odin @@ -0,0 +1,152 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/resource.h - definitions XSI resource operations + +foreign lib { + /* + Gets the nice value of the process, process group or user given. + + Note that a nice value can be -1, so checking for an error would mean clearing errno, doing the + call and then checking that this returns -1 and it has an errno. + + Returns: -1 (setting errno) on failure, the value otherwise + + Example: + pid := posix.getpid() + posix.set_errno(.NONE) + prio := posix.getpriority(.PROCESS, pid) + if err := posix.errno(); prio == -1 && err != .NONE { + // Handle error... + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html ]] + */ + getpriority :: proc(which: Which_Prio, who: id_t) -> c.int --- + + /* + Sets the nice value of the process, process group or user given. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html ]] + */ + setpriority :: proc(which: Which_Prio, who: id_t, value: c.int) -> result --- + + /* + Get a resource limit. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html ]] + */ + getrlimit :: proc(resource: Resource, rlp: ^rlimit) -> result --- + + /* + Set a resource limit. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html ]] + */ + setrlimit :: proc(resource: Resource, rlp: ^rlimit) -> result --- + + /* + Get resource usage. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrusage.html ]] + */ + @(link_name=LGETRUSAGE) + getrusage :: proc(who: Which_Usage, rusage: ^rusage) -> result --- +} + +Which_Prio :: enum c.int { + PROCESS = PRIO_PROCESS, + PGRP = PRIO_PGRP, + USER = PRIO_USER, +} + +Which_Usage :: enum c.int { + SELF = RUSAGE_SELF, + CHILDREN = RUSAGE_CHILDREN, +} + +Resource :: enum c.int { + // Maximum byte size of a core file that may be created by a process. + CORE = RLIMIT_CORE, + // Maximum amount of CPU time, in seconds, used by a process. + CPU = RLIMIT_CPU, + // Maximum size of data segment of the process, in bytes. + DATA = RLIMIT_DATA, + // Maximum size of a file, in bytes, that may be created by a process. + FSIZE = RLIMIT_FSIZE, + // A number one greater than the maximum value that the system may assign to a newly-created descriptor. + NOFILE = RLIMIT_NOFILE, + // The maximum size of the initial thread's stack, in bytes. + STACK = RLIMIT_STACK, + // Maximum size of total available memory of the process, in bytes. + AS = RLIMIT_AS, +} + +when ODIN_OS == .NetBSD { + @(private) LGETRUSAGE :: "__getrusage50" +} else { + @(private) LGETRUSAGE :: "getrusage" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + PRIO_PROCESS :: 0 + PRIO_PGRP :: 1 + PRIO_USER :: 2 + + rlim_t :: distinct c.uint64_t + + RLIM_INFINITY :: (rlim_t(1) << 63) - 1 + RLIM_SAVED_MAX :: RLIM_INFINITY + RLIM_SAVED_CUR :: RLIM_INFINITY + + RUSAGE_SELF :: 0 + RUSAGE_CHILDREN :: -1 + + rlimit :: struct { + rlim_cur: rlim_t, /* [PSX] the current (soft) limit */ + rlim_max: rlim_t, /* [PSX] the hard limit */ + } + + rusage :: struct { + ru_utime: timeval, /* [PSX] user time used */ + ru_stime: timeval, /* [PSX] system time used */ + + // Informational aliases for source compatibility with programs + // that need more information than that provided by standards, + // and which do not mind being OS-dependent. + + ru_maxrss: c.long, /* max resident set size (PL) */ + ru_ixrss: c.long, /* integral shared memory size (NU) */ + ru_idrss: c.long, /* integral unshared data (NU) */ + ru_isrss: c.long, /* integral unshared stack (NU) */ + ru_minflt: c.long, /* page reclaims (NU) */ + ru_majflt: c.long, /* page faults (NU) */ + ru_nswap: c.long, /* swaps (NU) */ + ru_inblock: c.long, /* block input operations (atomic) */ + ru_outblock: c.long, /* block output operations (atomic) */ + ru_msgsnd: c.long, /* messages sent (atomic) */ + ru_msgrcv: c.long, /* messages received (atomic) */ + ru_nsignals: c.long, /* signals received (atomic) */ + ru_nvcsw: c.long, /* voluntary context switches (atomic) */ + ru_nivcsw: c.long, /* involuntary " */ + } + + RLIMIT_CORE :: 4 + RLIMIT_CPU :: 0 + RLIMIT_DATA :: 2 + RLIMIT_FSIZE :: 1 + RLIMIT_NOFILE :: 8 + RLIMIT_STACK :: 3 + RLIMIT_AS :: 5 when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD else 10 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_select.odin b/core/sys/posix/sys_select.odin new file mode 100644 index 000000000..3392e02bc --- /dev/null +++ b/core/sys/posix/sys_select.odin @@ -0,0 +1,120 @@ +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/select.h - select types + +foreign lib { + /* + Examines the file descriptor sets to see whether some of their descriptors are ready for writing, + or have an exceptional condition pending, respectively. + + Returns: -1 (setting errno) on failure, total amount of bits set in the bit masks otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]] + */ + @(link_name=LPSELECT) + pselect :: proc( + nfds: c.int, + readfds: ^fd_set, + writefds: ^fd_set, + errorfds: ^fd_set, + timeout: ^timespec, + sigmask: ^sigset_t, + ) -> c.int --- + + /* + Equivalent to pselect() except a more specific timeout resolution (nanoseconds), + does not have a signal mask, and may modify the timeout. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]] + */ + @(link_name=LSELECT) + select :: proc( + nfds: c.int, + readfds: ^fd_set, + writefds: ^fd_set, + errorfds: ^fd_set, + timeout: ^timeval, + ) -> c.int --- +} + +when ODIN_OS == .NetBSD { + LPSELECT :: "__pselect50" + LSELECT :: "__select50" +} else { + LPSELECT :: "pselect" + LSELECT :: "select" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + suseconds_t :: distinct (c.int32_t when ODIN_OS == .Darwin || ODIN_OS == .NetBSD else c.long) + + timeval :: struct { + tv_sec: time_t, /* [PSX] seconds */ + tv_usec: suseconds_t, /* [PSX] microseconds */ + } + + // Maximum number of file descriptors in the fd_set structure. + FD_SETSIZE :: #config(POSIX_FD_SETSIZE, 256 when ODIN_OS == .NetBSD else 1024) + + @(private) + __NFDBITS :: size_of(c.int32_t) * 8 + + // NOTE: this seems correct for FreeBSD but they do use a set backed by the long type themselves (thus the align change). + @(private) + ALIGN :: align_of(c.long) when ODIN_OS == .FreeBSD else align_of(c.int32_t) + + fd_set :: struct #align(ALIGN) { + fds_bits: [(FD_SETSIZE / __NFDBITS) when (FD_SETSIZE % __NFDBITS) == 0 else (FD_SETSIZE / __NFDBITS) + 1]c.int32_t, + } + + @(private) + __check_fd_set :: #force_inline proc "contextless" (_a: FD, _b: rawptr) -> bool { + if _a < 0 { + set_errno(.EINVAL) + } + + if _a >= FD_SETSIZE { + set_errno(.EINVAL) + } + + return true + } + + FD_CLR :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) { + if __check_fd_set(_fd, _p) { + _p.fds_bits[cast(c.ulong)_fd / __NFDBITS] &= ~cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS)) + } + } + + FD_ISSET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) -> bool { + if __check_fd_set(_fd, _p) { + return bool(_p.fds_bits[cast(c.ulong)_fd / __NFDBITS] & cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS))) + } + + return false + } + + FD_SET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) { + if __check_fd_set(_fd, _p) { + _p.fds_bits[cast(c.ulong)_fd / __NFDBITS] |= cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS)) + } + } + + FD_ZERO :: #force_inline proc "contextless" (_p: ^fd_set) { + intrinsics.mem_zero(_p, size_of(fd_set)) + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_sem.odin b/core/sys/posix/sys_sem.odin new file mode 100644 index 000000000..3fcde325b --- /dev/null +++ b/core/sys/posix/sys_sem.odin @@ -0,0 +1,132 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/sem.h - XSI semaphore facility + +foreign lib { + /* + Provides various semaphore control operation as specified by cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semctl.html ]] + */ + @(link_name=LSEMCTL) + semctl :: proc(semid: FD, semnum: c.int, cmd: Sem_Cmd, arg: ^semun = nil) -> c.int --- + + /* + Returns the semaphore identifier associated with key. + + Returns: -1 (setting errno) on failure, a semaphore file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semget.html ]] + */ + semget :: proc(key: key_t, nsems: c.int, semflg: IPC_Flags) -> FD --- + + /* + Perform atomically a user-defined array of semaphore operations in array order on the set of + semaphores associated with the semaphore identifier specified by the argument semid. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semop.html ]] + */ + semop :: proc(semid: FD, sops: [^]sembuf, nsops: c.size_t) -> result --- +} + +Sem_Cmd :: enum c.int { + // Returns the value of semncnt. + GETNCNT = GETNCNT, + // Returns the value of sempid. + GETPID = GETPID, + // Return the value of semval. + GETVAL = GETVAL, + // Returns the value of semval for each semaphore in the semaphore set. + GETALL = GETALL, + // Returns the value of semzcnt. + GETZCNT = GETZCNT, + // Sets the value of semval to arg.val. + SETVAL = SETVAL, + // Sets the value of semval for each semaphore in the set. + SETALL = SETALL, +} + +semun :: struct #raw_union { + val: c.int, + buf: ^semid_ds, + array: [^]c.ushort, +} + +when ODIN_OS == .NetBSD { + @(private) LSEMCTL :: "__semctl50" +} else { + @(private) LSEMCTL :: "semctl" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + SEM_UNDO :: 0o10000 + + GETNCNT :: 3 + GETPID :: 4 + GETVAL :: 5 + GETALL :: 6 + GETZCNT :: 7 + SETVAL :: 8 + SETALL :: 9 + + when ODIN_OS == .Darwin { + // NOTE: this is #pragma pack(4) + + semid_ds :: struct #align(4) { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_base: c.int32_t, /* 32 bit base ptr for semaphore set */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_pad1: c.int32_t, + using _: struct #align(4) { + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + sem_pad2: c.int32_t, + sem_pad3: [4]c.int32_t, + }, + } + } else when ODIN_OS == .FreeBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_base: rawptr, /* 32 bit base ptr for semaphore set */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + } + } else when ODIN_OS == .NetBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + _sem_base: rawptr, /* 32 bit base ptr for semaphore set */ + } + } else when ODIN_OS == .OpenBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_pad1: c.long, + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + sem_pad2: c.long, + sem_pad3: [4]c.long, + } + } + + sembuf :: struct { + sem_num: c.ushort, /* [PSX] semaphore number */ + sem_op: c.short, /* [PSX] semaphore operation */ + sem_flg: c.short, /* [PSX] operation flags */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_shm.odin b/core/sys/posix/sys_shm.odin new file mode 100644 index 000000000..3bc883ce4 --- /dev/null +++ b/core/sys/posix/sys_shm.odin @@ -0,0 +1,146 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/shm.h = XSI shared memory facility + +foreign lib { + /* + Attaches the shared memory segment associated with the identifier + into the address space of the calling process. + + Returns: nil (setting errno) on failure, the address otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmat.html ]] + */ + shmat :: proc(shmid: FD, shmaddr: rawptr, shmflag: SHM_Flags) -> rawptr --- + + /* + Provides various shared memory operation as specified by the given cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmctl.html ]] + */ + @(link_name=LSHMCTL) + shmctl :: proc(shmid: FD, cmd: IPC_Cmd, buf: ^shmid_ds) -> result --- + + /* + Detaches the shared memory segment located at the address specified. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmdt.html ]] + */ + shmdt :: proc(shmaddr: rawptr) -> result --- + + /* + Returns the shared memory identifier associated with key. + + Returns: -1 (setting errno) on failure, the shared memory ID otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmget.html ]] + */ + shmget :: proc(key: key_t, size: c.size_t, shmflag: SHM_Flags) -> FD --- +} + +SHM_Flag_Bits :: enum c.int { + RDONLY = log2(SHM_RDONLY), + RND = log2(SHM_RND), +} +SHM_Flags :: bit_set[SHM_Flag_Bits; c.int] + +when ODIN_OS == .NetBSD { + @(private) LSHMCTL :: "__shmctl50" +} else { + @(private) LSHMCTL :: "shmctl" +} + +when ODIN_OS == .Darwin { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 16 * 1024 when ODIN_ARCH == .arm64 else 4096 + + shmatt_t :: distinct c.ushort + + // NOTE: this is #pragma pack(4) + + shmid_ds :: struct #align(4) { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + using _: struct #align(4) { + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + shm_internal: rawptr, + }, + } + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: PAGESIZE + + shmatt_t :: distinct c.uint + + when ODIN_OS == .FreeBSD { + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + } + } else { + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + _shm_internal: rawptr, + } + } + +} else when ODIN_OS == .OpenBSD { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 1 << 12 + + shmatt_t :: distinct c.short + + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.int, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + __shm_atimensec: c.long, + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + __shm_dtimensec: c.long, + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + __shm_ctimensec: c.long, + _shm_internal: rawptr, + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_socket.odin b/core/sys/posix/sys_socket.odin new file mode 100644 index 000000000..36c3c1467 --- /dev/null +++ b/core/sys/posix/sys_socket.odin @@ -0,0 +1,495 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// sys/socket.h - main sockets header + +#assert(Protocol.IP == Protocol(0), "socket() assumes this") + +foreign libc { + /* + Creates a socket. + + Returns: -1 (setting errno) on failure, file descriptor of socket otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html ]] + */ + @(link_name=LSOCKET) + socket :: proc(domain: AF, type: Sock, protocol: Protocol = .IP) -> FD --- + + /* + Extracts the first connection on the queue of pending connections. + + Blocks (if not O_NONBLOCK) if there is no pending connection. + + Returns: -1 (setting errno) on failure, file descriptor of accepted socket otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html ]] + */ + accept :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> FD --- + + /* + Assigns a local socket address to the socket. + + Example: + sfd := posix.socket(.UNIX, .STREAM) + if sfd == -1 { + /* Handle error */ + } + + addr: posix.sockaddr_un + addr.sun_family = .UNIX + copy(addr.sun_path[:], "/somepath\x00") + + if posix.bind(sfd, (^posix.sockaddr)(&addr), size_of(addr)) != .OK { + /* Handle error */ + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html ]] + */ + bind :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result --- + + /* + Attempt to make a connection. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html ]] + */ + connect :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result --- + + /* + Get the peer address of the specified socket. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html ]] + */ + getpeername :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result --- + + /* + Get the socket name. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html ]] + */ + getsockname :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result --- + + /* + Retrieves the value for the option specified by option_name. + + level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET` + to specify the socket local level. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html ]] + */ + getsockopt :: proc( + socket: FD, + level: c.int, + option_name: Sock_Option, + option_value: rawptr, + option_len: ^socklen_t, + ) -> result --- + + /* + Sets the specified option. + + level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET` + to specify the socket local level. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html ]] + */ + setsockopt :: proc( + socket: FD, + level: c.int, + option_name: Sock_Option, + option_value: rawptr, + option_len: socklen_t, + ) -> result --- + + /* + Mark the socket as a socket accepting connections. + + backlog provides a hint to limit the number of connections on the listen queue. + Implementation may silently reduce the backlog, additionally `SOMAXCONN` specifies the maximum + an implementation has to support. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html ]] + */ + listen :: proc(socket: FD, backlog: c.int) -> result --- + + /* + Receives a message from a socket. + + Blocks (besides with O_NONBLOCK) if there is nothing to receive. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html ]] + */ + recv :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t --- + + /* + Receives a message from a socket. + + Equivalent to recv() but retrieves the source address too. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html ]] + */ + recvfrom :: proc( + socket: FD, + buffer: rawptr, + length: c.size_t, + flags: Msg_Flags, + address: ^sockaddr, + address_len: ^socklen_t, + ) -> c.ssize_t --- + + /* + Receives a message from a socket. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html ]] + */ + recvmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html ]] + */ + send :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html ]] + */ + sendmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + If the socket is connectionless, the dest_addr is used to send to. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html ]] + */ + sendto :: proc( + socket: FD, + message: rawptr, + length: c.size_t, + flags: Msg_Flags, + dest_addr: ^sockaddr, + dest_len: socklen_t, + ) -> c.ssize_t --- + + /* + Shuts down a socket end or both. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html ]] + */ + shutdown :: proc(socket: FD, how: Shut) -> result --- + + /* + Determine wheter a socket is at the out-of-band mark. + + Returns: -1 (setting errno) on failure, 0 if not at the mark, 1 if it is + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sockatmark.html ]] + */ + sockatmark :: proc(socket: FD) -> c.int --- + + /* + Create a pair of connected sockets. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html ]] + */ + socketpair :: proc(domain: AF, type: Sock, protocol: Protocol, socket_vector: ^[2]FD) -> result --- +} + +AF_UNSPEC :: 0 + +AF :: enum c.int { + // Unspecified. + UNSPEC = AF_UNSPEC, + // Internet domain sockets for use with IPv4 addresses. + INET = AF_INET, + // Internet domain sockets for use with IPv6 addresses. + INET6 = AF_INET6, + // UNIX domain sockets. + UNIX = AF_UNIX, +} + +sa_family_t :: enum _sa_family_t { + // Unspecified. + UNSPEC = AF_UNSPEC, + // Internet domain sockets for use with IPv4 addresses. + INET = AF_INET, + // Internet domain sockets for use with IPv6 addresses. + INET6 = AF_INET6, + // UNIX domain sockets. + UNIX = AF_UNIX, +} + +Sock :: enum c.int { + // Datagram socket. + DGRAM = SOCK_DGRAM, + // Raw Protocol Interface. + RAW = SOCK_RAW, + // Sequenced-packet socket. + SEQPACKET = SOCK_SEQPACKET, + // Byte-stream socket. + STREAM = SOCK_STREAM, +} + +Shut :: enum c.int { + // Disables further receive operations. + RD = SHUT_RD, + // Disables further send and receive operations. + RDWR = SHUT_RDWR, + // Disables further send operations. + WR = SHUT_WR, +} + +Msg_Flag_Bits :: enum c.int { + // Control data truncated. + CTRUNC = log2(MSG_CTRUNC), + // Send without using routing table. + DONTROUTE = log2(MSG_DONTROUTE), + // Terminates a record (if supported by protocol). + EOR = log2(MSG_EOR), + // Out-of-band data. + OOB = log2(MSG_OOB), + // No SIGPIPE is generated when an attempt to send is made on a stream-oriented socket that is + // no longer connected. + NOSIGNAL = log2(MSG_NOSIGNAL), + // Leave received data in queue. + PEEK = log2(MSG_PEEK), + // Normal data truncated. + TRUNC = log2(MSG_TRUNC), + // Attempt to fill the read buffer. + WAITALL = log2(MSG_WAITALL), +} +Msg_Flags :: bit_set[Msg_Flag_Bits; c.int] + +Sock_Option :: enum c.int { + // Transmission of broadcast message is supported. + BROADCAST = SO_BROADCAST, + // Debugging information is being recorded. + DEBUG = SO_DEBUG, + // Bypass normal routing. + DONTROUTE = SO_DONTROUTE, + // Socket error status. + ERROR = SO_ERROR, + // Connections are kept alive with periodic messages. + KEEPALIVE = SO_KEEPALIVE, + // Socket lingers on close. + LINGER = SO_LINGER, + // Out-of-band data is transmitted in line. + OOBINLINE = SO_OOBINLINE, + // Receive buffer size. + RCVBUF = SO_RCVBUF, + // Receive low water mark. + RCVLOWAT = SO_RCVLOWAT, + // Receive timeout. + RCVTIMEO = SO_RCVTIMEO, + // Reuse of local addresses is supported. + REUSEADDR = SO_REUSEADDR, + // Send buffer size. + SNDBUF = SO_SNDBUF, + // Send low water mark. + SNDLOWAT = SO_SNDLOWAT, + // Send timeout. + SNDTIMEO = SO_SNDTIMEO, + // Socket type. + TYPE = SO_TYPE, +} + +when ODIN_OS == .NetBSD { + @(private) LSOCKET :: "__socket30" +} else { + @(private) LSOCKET :: "socket" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + socklen_t :: distinct c.uint + + _sa_family_t :: distinct c.uint8_t + + sockaddr :: struct { + sa_len: c.uint8_t, /* total length */ + sa_family: sa_family_t, /* [PSX] address family */ + sa_data: [14]c.char, /* [PSX] socket address */ + } + + + when ODIN_OS == .OpenBSD { + @(private) + _SS_PAD1SIZE :: 6 + @(private) + _SS_PAD2SIZE :: 240 + } else { + @(private) + _SS_MAXSIZE :: 128 + @(private) + _SS_ALIGNSIZE :: size_of(c.int64_t) + @(private) + _SS_PAD1SIZE :: _SS_ALIGNSIZE - size_of(c.uint8_t) - size_of(sa_family_t) + @(private) + _SS_PAD2SIZE :: _SS_MAXSIZE - size_of(c.uint8_t) - size_of(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE + } + + sockaddr_storage :: struct { + ss_len: c.uint8_t, /* address length */ + ss_family: sa_family_t, /* [PSX] address family */ + __ss_pad1: [_SS_PAD1SIZE]c.char, + __ss_align: c.int64_t, /* force structure storage alignment */ + __ss_pad2: [_SS_PAD2SIZE]c.char, + } + + msghdr :: struct { + msg_name: rawptr, /* [PSX] optional address */ + msg_namelen: socklen_t, /* [PSX] size of address */ + msg_iov: [^]iovec, /* [PSX] scatter/gather array */ + msg_iovlen: c.int, /* [PSX] members in msg_iov */ + msg_control: rawptr, /* [PSX] ancillary data */ + msg_controllen: socklen_t, /* [PSX] ancillary data buffer length */ + msg_flags: Msg_Flags, /* [PSX] flags on received message */ + } + + cmsghdr :: struct { + cmsg_len: socklen_t, /* [PSX] data byte count, including cmsghdr */ + cmsg_level: c.int, /* [PSX] originating protocol */ + cmsg_type: c.int, /* [PSX] protocol-specific type */ + } + + SCM_RIGHTS :: 0x01 + + @(private) + __ALIGN32 :: #force_inline proc "contextless" (p: uintptr) -> uintptr { + __ALIGNBYTES32 :: size_of(c.uint32_t) - 1 + return (p + __ALIGNBYTES32) &~ __ALIGNBYTES32 + } + + // Returns a pointer to the data array. + CMSG_DATA :: #force_inline proc "contextless" (cmsg: ^cmsghdr) -> [^]c.uchar { + return ([^]c.uchar)(uintptr(cmsg) + __ALIGN32(size_of(cmsghdr))) + } + + // Returns a pointer to the next cmsghdr or nil. + CMSG_NXTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr, cmsg: ^cmsghdr) -> ^cmsghdr { + if cmsg == nil { + return CMSG_FIRSTHDR(mhdr) + } + + ptr := uintptr(cmsg) + __ALIGN32(uintptr(cmsg.cmsg_len)) + if ptr + __ALIGN32(size_of(cmsghdr)) > uintptr(mhdr.msg_control) + uintptr(mhdr.msg_controllen) { + return nil + } + + return (^cmsghdr)(ptr) + } + + // Returns a pointer to the first cmsghdr or nil. + CMSG_FIRSTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr) -> ^cmsghdr { + if mhdr.msg_controllen >= size_of(cmsghdr) { + return (^cmsghdr)(mhdr.msg_control) + } + + return nil + } + + linger :: struct { + l_onoff: c.int, /* [PSX] indicates whether linger option is enabled */ + l_linger: c.int, /* [PSX] linger time in seconds */ + } + + SOCK_DGRAM :: 2 + SOCK_RAW :: 3 + SOCK_SEQPACKET :: 5 + SOCK_STREAM :: 1 + + // Options to be accessed at socket level, not protocol level. + SOL_SOCKET :: 0xffff + + SO_ACCEPTCONN :: 0x0002 + SO_BROADCAST :: 0x0020 + SO_DEBUG :: 0x0001 + SO_DONTROUTE :: 0x0010 + SO_ERROR :: 0x1007 + SO_KEEPALIVE :: 0x0008 + SO_OOBINLINE :: 0x0100 + SO_RCVBUF :: 0x1002 + SO_RCVLOWAT :: 0x1004 + SO_REUSEADDR :: 0x0004 + SO_SNDBUF :: 0x1001 + SO_SNDLOWAT :: 0x1003 + SO_TYPE :: 0x1008 + + when ODIN_OS == .Darwin { + SO_LINGER :: 0x1080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .FreeBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .NetBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x100c + SO_SNDTIMEO :: 0x100b + } else when ODIN_OS == .OpenBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } + + // The maximum backlog queue length for listen(). + SOMAXCONN :: 128 + + MSG_CTRUNC :: 0x20 + MSG_DONTROUTE :: 0x4 + MSG_EOR :: 0x8 + MSG_OOB :: 0x1 + MSG_PEEK :: 0x2 + MSG_TRUNC :: 0x10 + MSG_WAITALL :: 0x40 + + when ODIN_OS == .Darwin { + MSG_NOSIGNAL :: 0x80000 + } else when ODIN_OS == .FreeBSD { + MSG_NOSIGNAL :: 0x00020000 + } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + MSG_NOSIGNAL :: 0x0400 + } + + AF_INET :: 2 + AF_UNIX :: 1 + + when ODIN_OS == .Darwin { + AF_INET6 :: 30 + } else when ODIN_OS == .FreeBSD { + AF_INET6 :: 28 + } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + AF_INET6 :: 24 + } + + SHUT_RD :: 0 + SHUT_RDWR :: 2 + SHUT_WR :: 1 + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/sys_stat.odin b/core/sys/posix/sys_stat.odin new file mode 100644 index 000000000..dd66d7d14 --- /dev/null +++ b/core/sys/posix/sys_stat.odin @@ -0,0 +1,432 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/stat.h - data returned by the stat() function + +foreign lib { + + /* + Equivalent to either stat or lstat (based on the SYMLINK_NOFOLLOW bit in flags) + but resolves relative paths based on the given fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name="fstatat" + INODE_SUFFIX) + fstatat :: proc(fd: FD, path: cstring, buf: ^stat_t, flag: AT_Flags) -> result --- + + /* + Obtain information about a "file" at the given path. + + Follows symbolic links. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name=LSTAT) + stat :: proc(path: cstring, buf: ^stat_t) -> result --- + + /* + Obtain information about an open file. + + Follows symbol links. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstat.html ]] + */ + @(link_name=LFSTAT) + fstat :: proc(fildes: FD, buf: ^stat_t) -> result --- + + /* + Obtain information about a "file" at the given path. + + Does not follow symlinks (will stat the symlink itself). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name=LLSTAT) + lstat :: proc(path: cstring, buf: ^stat_t) -> result --- + + /* + Change the mode of a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html ]] + */ + chmod :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to chmod but takes an open file descriptor. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html ]] + */ + fchmod :: proc(fd: FD, mode: mode_t) -> result --- + + /* + Equivalent to chmod but follows (or doesn't) symlinks based on the flag and resolves + relative paths from the given fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html ]] + */ + fchmodat :: proc(fd: FD, path: cstring, mode: mode_t, flag: AT_Flags) -> result --- + + /* + Make a directory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html ]] + */ + mkdir :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to mkdir but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html ]] + */ + mkdirat :: proc(fd: FD, path: cstring, mode: mode_t) -> result --- + + /* + Make a FIFO special file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifo.html ]] + */ + mkfifo :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to mkfifo but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifo.html ]] + */ + mkfifoat :: proc(fd: FD, path: cstring, mode: mode_t) -> result --- + + /* + Make directory, special file, or regular file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html ]] + */ + @(link_name=LMKNOD) + mknod :: proc(path: cstring, mode: mode_t, dev: dev_t) -> result --- + + /* + Equivalent to mknod but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html ]] + */ + mknodat :: proc(fd: FD, path: cstring, mode: mode_t, dev: dev_t) -> result --- + + /* + Sets the file access and modification time of the given file descriptor. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html ]] + */ + futimens :: proc(fd: FD, times: ^[2]timespec) -> result --- + + /* + Equivalent to futimens. + Relative directories are based on fd. + Symlinks may or may not be followed based on the flags. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html ]] + */ + utimensat :: proc(fd: FD, path: cstring, times: ^[2]timespec, flag: AT_Flags) -> result --- + + /* + Set and get the file mode creation flags. + + Makes the file mode permissions bits in cmask the new default for the process. + + Returns: the previous value + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/umask.html ]] + */ + umask :: proc(cmask: mode_t) -> mode_t --- +} + +// Read, write, execute user. +S_IRWXU :: mode_t{ .IRUSR, .IWUSR, .IXUSR } +// Read, write, execute group. +S_IRWXG :: mode_t{ .IRGRP, .IWGRP, .IXGRP } +// Read, write, execute other. +S_IRWXO :: mode_t{ .IROTH, .IWOTH, .IXOTH } + +Mode_Bits :: enum c.int { + // File type: + + IFCHR = log2(_S_IFCHR), /* Character special */ + IFIFO = log2(_S_IFIFO), /* FIFO special */ + IFREG = log2(_S_IFREG), /* Regular */ + IFDIR = log2(_S_IFDIR), /* Directory */ + + // Permissions: + + IRUSR = log2(_S_IRUSR), /* R for owner */ + IWUSR = log2(_S_IWUSR), /* W for owner */ + IXUSR = log2(_S_IXUSR), /* X for owner */ + + IRGRP = log2(_S_IRGRP), /* R for group */ + IWGRP = log2(_S_IWGRP), /* W for group */ + IXGRP = log2(_S_IXGRP), /* X for group */ + + IROTH = log2(_S_IROTH), /* R for other */ + IWOTH = log2(_S_IWOTH), /* W for other */ + IXOTH = log2(_S_IXOTH), /* X for other */ + + ISUID = log2(_S_ISUID), /* Set user ID on execution */ + ISGID = log2(_S_ISGID), /* Set group ID on execution */ + ISVXT = log2(_S_ISVTX), /* On directories, restricted deletion flag */ +} +mode_t :: bit_set[Mode_Bits; _mode_t] +#assert(size_of(mode_t) == size_of(_mode_t)) + +S_IFMT :: mode_t{ .IFCHR, .IFREG, .IFDIR, .IFIFO } +S_IFSOCK :: mode_t{ .IFREG, .IFDIR } +S_IFLNK :: mode_t{ .IFREG, .IFCHR } +S_IFBLK :: mode_t{ .IFDIR, .IFCHR } +S_IFIFO :: mode_t{ .IFIFO } +S_IFCHR :: mode_t{ .IFCHR } +S_IFDIR :: mode_t{ .IFDIR } +S_IFREG :: mode_t{ .IFREG } + +#assert(_S_IFMT == _S_IFCHR|_S_IFREG|_S_IFDIR|_S_IFIFO) +#assert(_S_IFSOCK == _S_IFREG|_S_IFDIR) +#assert(_S_IFLNK == _S_IFREG|_S_IFCHR) +#assert(_S_IFBLK == _S_IFDIR|_S_IFCHR) + +// Test for a block special file. +S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFBLK +} + +// Test for a character special file. +S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFCHR +} + +// Test for a pipe or FIFO special file. +S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFIFO +} + +// Test for a regular file. +S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFREG +} + +// Test for a directory. +S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFDIR +} + +// Test for a symbolic link. +S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFLNK +} + +// Test for a socket. +S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFSOCK +} + +_S_IRWXU :: 0o000700 +_S_IRUSR :: 0o000400 +_S_IWUSR :: 0o000200 +_S_IXUSR :: 0o000100 + +_S_IRWXG :: 0o000070 +_S_IRGRP :: 0o000040 +_S_IWGRP :: 0o000020 +_S_IXGRP :: 0o000010 + +_S_IRWXO :: 0o000007 +_S_IROTH :: 0o000004 +_S_IWOTH :: 0o000002 +_S_IXOTH :: 0o000001 + +_S_ISUID :: 0o004000 +_S_ISGID :: 0o002000 +_S_ISVTX :: 0o001000 + +_S_IFBLK :: 0o060000 +_S_IFCHR :: 0o020000 +_S_IFIFO :: 0o010000 +_S_IFREG :: 0o100000 +_S_IFDIR :: 0o040000 +_S_IFLNK :: 0o120000 +_S_IFSOCK :: 0o140000 + +_S_IFMT :: 0o170000 + +when ODIN_OS == .NetBSD { + @(private) LSTAT :: "__stat50" + @(private) LFSTAT :: "__fstat50" + @(private) LLSTAT :: "__lstat50" + @(private) LMKNOD :: "__mknod50" +} else { + @(private) LSTAT :: "stat" + INODE_SUFFIX + @(private) LFSTAT :: "fstat" + INODE_SUFFIX + @(private) LLSTAT :: "lstat" + INODE_SUFFIX + @(private) LMKNOD :: "mknod" +} + +when ODIN_OS == .Darwin { + + dev_t :: distinct c.int32_t + nlink_t :: distinct c.uint16_t + _mode_t :: distinct c.uint16_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_mode: mode_t, /* [XSI] mode of file */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_ino: ino_t, /* [XSI] file serial number */ + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint32_t, /* file generation number */ + st_lspare: c.int32_t, /* RESERVED */ + st_qspare: [2]c.int64_t, /* RESERVED */ + } + + UTIME_NOW :: -1 + UTIME_OMIT :: -2 + +} else when ODIN_OS == .FreeBSD { + + dev_t :: distinct c.uint64_t + nlink_t :: distinct c.uint64_t + _mode_t :: distinct c.uint16_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + when ODIN_ARCH == .i386 { + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_mode: mode_t, /* [XSI] mode of file */ + st_padding0: c.int16_t, + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_padding1: c.int32_t, + st_rdev: dev_t, /* [XSI] device ID */ + st_atim_ext: c.int32_t, + st_atim: timespec, /* [XSI] time of last access */ + st_mtim_ext: c.int32_t, + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim_ext: c.int32_t, + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [10]c.uint64_t, + } + } else { + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_mode: mode_t, /* [XSI] mode of file */ + st_padding0: c.int16_t, + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_padding1: c.int32_t, + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [10]c.uint64_t, + } + } + + UTIME_NOW :: -1 + UTIME_OMIT :: -2 + +} else when ODIN_OS == .NetBSD { + + dev_t :: distinct c.uint64_t + nlink_t :: distinct c.uint32_t + _mode_t :: distinct c.uint32_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_mode: mode_t, /* [XSI] mode of file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [2]c.uint32_t, + } + + UTIME_NOW :: (1 << 30) - 1 + UTIME_OMIT :: (1 << 30) - 2 + +} else when ODIN_OS == .OpenBSD { + + dev_t :: distinct c.int32_t + nlink_t :: distinct c.uint32_t + _mode_t :: distinct c.uint32_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_mode: mode_t, /* [XSI] mode of file */ + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.int32_t, + st_birthtimespec: timespec, + } + + UTIME_NOW :: -2 + UTIME_OMIT :: -1 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_statvfs.odin b/core/sys/posix/sys_statvfs.odin new file mode 100644 index 000000000..eb6c16806 --- /dev/null +++ b/core/sys/posix/sys_statvfs.odin @@ -0,0 +1,135 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/statvfs.h - VFS File System information structure + +foreign lib { + + /* + Obtains information about the file system containing the fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/statvfs.html ]] + */ + @(link_name=LFSTATVFS) + fstatvfs :: proc(fildes: FD, buf: ^statvfs_t) -> result --- + + /* + Obtains information about the file system containing the file named by path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/statvfs.html ]] + */ + @(link_name=LSTATVFS) + statvfs :: proc(path: cstring, buf: ^statvfs_t) -> result --- +} + +VFS_Flag_Bits :: enum c.ulong { + // Read-only file system. + RDONLY = log2(ST_RDONLY), + // Does not support the semantics of the ST_ISUID and ST_ISGID file mode bits. + NOSUID = log2(ST_NOSUID), +} +VFS_Flags :: bit_set[VFS_Flag_Bits; c.ulong] + +when ODIN_OS == .NetBSD { + @(private) LFSTATVFS :: "__fstatvfs90" + @(private) LSTATVFS :: "__statvfs90" +} else { + @(private) LFSTATVFS :: "fstatvfs" + @(private) LSTATVFS :: "statvfs" +} + +when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + + fsblkcnt_t :: distinct c.uint + + statvfs_t :: struct { + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fsid: c.ulong, /* [PSX] file system ID */ + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 + +} else when ODIN_OS == .FreeBSD { + + fsblkcnt_t :: distinct c.uint64_t + + statvfs_t :: struct { + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_bsize: c.ulong, /* [PSX] file system block size */ + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_fsid: c.ulong, /* [PSX] file system ID */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 + +} else when ODIN_OS == .NetBSD { + + fsblkcnt_t :: distinct c.uint64_t + + @(private) + _VFS_NAMELEN :: 1024 + + @(private) + fsid_t :: struct { + __fsid_val: [2]c.int, + } + + statvfs_t :: struct { + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_iosize: c.ulong, + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_bresvd: fsblkcnt_t, + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fresvd: fsblkcnt_t, + f_syncreads: c.uint64_t, + f_syncwrites: c.uint64_t, + f_asyncreads: c.uint64_t, + f_asyncwrites: c.uint64_t, + f_fsidx: fsid_t, + f_fsid: c.ulong, /* [PSX] file system ID */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + f_owner: uid_t, + f_spare: [4]c.uint64_t, + f_fstypename: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntonname: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntfromname: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntfromlabel: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000008 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_time.odin b/core/sys/posix/sys_time.odin new file mode 100644 index 000000000..093fdd688 --- /dev/null +++ b/core/sys/posix/sys_time.odin @@ -0,0 +1,82 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/time.h - time types + +foreign lib { + /* + Store the current value of timer into value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getitimer.html ]] + */ + @(link_name=LGETITIMER) + getitimer :: proc(which: ITimer, value: ^itimerval) -> result --- + + /* + Set the timer to the value given, and store the previous value in ovalue if it is not nil. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getitimer.html ]] + */ + @(link_name=LSETITIMER) + setitimer :: proc(which: ITimer, value: ^itimerval, ovalue: ^itimerval) -> result --- + + /* + Obtains the current time. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gettimeofday.html ]] + */ + @(link_name=LGETTIMEOFDAY) + gettimeofday :: proc(tp: ^timeval, tzp: rawptr = nil) -> result --- + + /* + Sets the access and modification times of the file at the given path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimes.html ]] + */ + @(link_name=LUTIMES) + utimes :: proc(path: cstring, times: ^[2]timeval) -> result --- +} + +ITimer :: enum c.int { + // Decrements in real time. + REAL = ITIMER_REAL, + // Decrements in process virtual time, only when the process is executing. + VIRTUAL = ITIMER_VIRTUAL, + // Decrements both in process virtual time and when the system is running on + // behalf of the process. + PROF = ITIMER_PROF, +} + +when ODIN_OS == .NetBSD { + @(private) LGETITIMER :: "__getitimer50" + @(private) LSETITIMER :: "__setitimer50" + @(private) LGETTIMEOFDAY :: "__gettimeofday50" + @(private) LUTIMES :: "__utimes50" +} else { + @(private) LGETITIMER :: "getitimer" + @(private) LSETITIMER :: "setitimer" + @(private) LGETTIMEOFDAY :: "gettimeofday" + @(private) LUTIMES :: "utimes" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + itimerval :: struct { + it_interval: timeval, /* [PSX] timer interval */ + it_value: timeval, /* [PSX] current value */ + } + + ITIMER_REAL :: 0 + ITIMER_VIRTUAL :: 1 + ITIMER_PROF :: 2 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_times.odin b/core/sys/posix/sys_times.odin new file mode 100644 index 000000000..685ced515 --- /dev/null +++ b/core/sys/posix/sys_times.odin @@ -0,0 +1,38 @@ +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/times.h - file access and modification times structure + +foreign lib { + /* + Get time accounting information. + + Returns: -1 (setting errno) on failure, the elapsed real time, since an arbitrary point in the past + */ + @(link_name=LTIMES) + times :: proc(buffer: ^tms) -> clock_t --- +} + +when ODIN_OS == .NetBSD { + @(private) LTIMES :: "__times13" +} else { + @(private) LTIMES :: "times" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + tms :: struct { + tms_utime: clock_t, /* [PSX] user CPU time */ + tms_stime: clock_t, /* [PSX] system CPU time */ + tms_cutime: clock_t, /* [PSX] terminated children user CPU time */ + tms_cstime: clock_t, /* [PSX] terminated children system CPU time */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_uio.odin b/core/sys/posix/sys_uio.odin new file mode 100644 index 000000000..01664e576 --- /dev/null +++ b/core/sys/posix/sys_uio.odin @@ -0,0 +1,42 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// sys/uio.h - definitions for vector I/O operations + +foreign libc { + /* + Equivalent to read() but takes a vector of inputs. + + iovcnt can be 0..=IOV_MAX in length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html ]] + */ + readv :: proc(fildes: FD, iov: [^]iovec, iovcnt: c.int) -> c.ssize_t --- + + /* + Equivalent to write() but takes a vector of inputs. + + iovcnt can be 0..=IOV_MAX in length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html ]] + */ + writev :: proc(fildes: FD, iov: [^]iovec, iovcnt: c.int) -> c.ssize_t --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + iovec :: struct { + iov_base: rawptr, /* [PSX] base address of I/O memory region */ + iov_len: c.size_t, /* [PSX] size of the region iov_base points to */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_un.odin b/core/sys/posix/sys_un.odin new file mode 100644 index 000000000..146882051 --- /dev/null +++ b/core/sys/posix/sys_un.odin @@ -0,0 +1,17 @@ +package posix + +import "core:c" + +// sys/un.h = definitions for UNIX domain sockets + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + sockaddr_un :: struct { + sun_len: c.uchar, /* sockaddr len including nil */ + sun_family: sa_family_t, /* [PSX] address family */ + sun_path: [104]c.char, /* [PSX] socket pathname */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_utsname.odin b/core/sys/posix/sys_utsname.odin new file mode 100644 index 000000000..803f40ffd --- /dev/null +++ b/core/sys/posix/sys_utsname.odin @@ -0,0 +1,55 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/utsname.h = system name structure + +foreign lib { + /* + Stores information identifying the current system in the given structure. + + Returns: non-negative on success, -1 (setting errno) on failure + + NOTE: have a look at `core:sys/info` for similar/better system information. + + Example: + uname: posix.utsname + posix.uname(&uname) + fmt.printfln("%#v", uname) + + Possible Output: + utsname{ + sysname = Darwin, + nodename = Laytans-MacBook-Pro.local, + release = 23.5.0, + version = Darwin Kernel Version 23.5.0: Wed May 1 20:16:51 PDT 2024; root:xnu-11331.111.3~1/RELEASE_ARM64_T8103, + machine = arm64, + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/uname.html ]] + */ + uname :: proc(uname: ^utsname) -> c.int --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + @(private) + _SYS_NAMELEN :: 256 + + utsname :: struct { + sysname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of OS */ + nodename: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of this network node */ + release: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] release level */ + version: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] version level */ + machine: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] hardware type */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_wait.odin b/core/sys/posix/sys_wait.odin new file mode 100644 index 000000000..8421c8bfd --- /dev/null +++ b/core/sys/posix/sys_wait.odin @@ -0,0 +1,380 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/wait.h - declarations for waiting + +foreign lib { + /* + Obtains status information pertaining to one of the caller's child processes. + + Returns: -1 (setting errno) on failure or signal on calling process, the pid of the process that caused the return otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html ]] + */ + wait :: proc(stat_loc: ^c.int) -> pid_t --- + + /* + Obtains status information pertaining to the given pid specifier. + + If pid is -1, status is requested for any child process. + If pid is greater than 0, it specifies the process ID of a single child process. + If pid is 0, it specifies any child process whose process group ID is equal to that of the call. + If pid is < -1, status is requested for any child whose process group ID is the absolute value of pid. + + Returns: -1 (setting errno) on failure or signal on calling process, 0 if NOHANG and status is not available, the pid of the process that caused the return otherwise + + Example: + // The following example demonstrates the use of waitpid(), fork(), and the macros used to + // interpret the status value returned by waitpid() (and wait()). The code segment creates a + // child process which does some unspecified work. Meanwhile the parent loops performing calls + // to waitpid() to monitor the status of the child. The loop terminates when child termination + // is detected. + + child_pid := posix.fork(); switch child_pid { + case -1: // `fork` failed. + panic("fork failed") + + case 0: // This is the child. + + // Do some work... + + case: + for { + status: i32 + wpid := posix.waitpid(child_pid, &status, { .UNTRACED, .CONTINUED }) + if wpid == -1 { + panic("waitpid failure") + } + + switch { + case posix.WIFEXITED(status): + fmt.printfln("child exited, status=%v", posix.WEXITSTATUS(status)) + case posix.WIFSIGNALED(status): + fmt.printfln("child killed (signal %v)", posix.WTERMSIG(status)) + case posix.WIFSTOPPED(status): + fmt.printfln("child stopped (signal %v", posix.WSTOPSIG(status)) + case posix.WIFCONTINUED(status): + fmt.println("child continued") + case: + // Should never happen. + fmt.println("unexpected status (%x)", status) + } + + if posix.WIFEXITED(status) || posix.WIFSIGNALED(status) { + break + } + } + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html ]] + */ + waitpid :: proc(pid: pid_t, stat_loc: ^c.int, options: Wait_Flags) -> pid_t --- + + /* + Obtains status information pertaining to the given idtype_t and id specifier. + + Returns: 0 if WNOHANG and no status available, 0 if child changed state, -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html ]] + */ + waitid :: proc(idtype: idtype_t, id: id_t, infop: ^siginfo_t, options: Wait_Flags) -> c.int --- +} + +// If terminated normally. +WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFEXITED(x) +} + +// If WIFEXITED is true, returns the exit status. +WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return _WEXITSTATUS(x) +} + +// If terminated due to an uncaught signal. +WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFSIGNALED(x) +} + +// If WIFSIGNALED is true, returns the signal. +WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return _WTERMSIG(x) +} + +// If status was returned for a child process that is currently stopped. +WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFSTOPPED(x) +} + +// If WIFSTOPPED, the signal that caused the child process to stop. +WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return _WSTOPSIG(x) +} + +// If status was returned for a child process that has continued from a job control stop. +WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFCONTINUED(x) +} + +idtype_t :: enum c.int { + // Wait for any children and `id` is ignored. + P_ALL, + // Wait for any child wiith a process group ID equal to `id`. + P_PID, + // Wait for any child with a process group ID equal to `id`. + P_PGID, +} + +Wait_Flag_Bits :: enum c.int { + // Report the status of any continued child process specified by pid whose status has not been + // reported since it continued from a job control stop. + CONTINUED = log2(WCONTINUED), + // Don't suspend execution of the calling thread if status is not immediately available for one + // of the child processes specified by pid. + NOHANG = log2(WNOHANG), + // The status of any child process specified by pid that are stopped, and whose status has not + // yet been reported since they stopped, shall also be reported to the requesting process. + UNTRACED = log2(WUNTRACED), + + // Following are only available on `waitid`, not `waitpid`. + + // Wait for processes that have exited. + EXITED = log2(WEXITED), + // Keep the process whose status is returned in a waitable state, so it may be waited on again. + NOWAIT = log2(WNOWAIT), + // Children that have stopped upon receipt of a signal, and whose status either hasn't been reported + // or has been reported but that report was called with NOWAIT. + STOPPED = log2(WSTOPPED), +} +Wait_Flags :: bit_set[Wait_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + id_t :: distinct c.uint + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000004 + WNOWAIT :: 0x00000020 + WSTOPPED :: 0x00000008 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && WSTOPSIG(x) != .SIGCONT + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x >> 8) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && WSTOPSIG(x) == .SIGCONT + } + +} else when ODIN_OS == .FreeBSD { + + id_t :: distinct c.int64_t + + WCONTINUED :: 4 + WNOHANG :: 1 + WUNTRACED :: 2 + + WEXITED :: 16 + WNOWAIT :: 8 + WSTOPPED :: 2 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 && x != c.int(Signal.SIGCONT) + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x >> 8) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == c.int(Signal.SIGCONT) + } +} else when ODIN_OS == .NetBSD { + + id_t :: distinct c.uint32_t + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000020 + WNOWAIT :: 0x00010000 + WSTOPPED :: 0x00000002 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return c.int((c.uint(x) >> 8) & 0xff) + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return !WIFSTOPPED(x) && !WIFCONTINUED(x) && !WIFEXITED(x) + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && !WIFCONTINUED(x) + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(c.int((c.uint(x) >> 8) & 0xff)) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == 0xffff + } + +} else when ODIN_OS == .OpenBSD { + + id_t :: distinct c.uint32_t + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000020 + WNOWAIT :: 0x00010000 + WSTOPPED :: 0x00000002 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + @(private) + _WCONTINUED :: 0o177777 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return (x >> 8) & 0x000000ff + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & 0xff) == _WSTOPPED + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal((x >> 8) & 0xff) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & _WCONTINUED) == _WCONTINUED + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/termios.odin b/core/sys/posix/termios.odin new file mode 100644 index 000000000..c73936d58 --- /dev/null +++ b/core/sys/posix/termios.odin @@ -0,0 +1,601 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// termios.h - define values for termios + +foreign lib { + /* + Get the input baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html ]] + */ + cfgetispeed :: proc(termios_p: ^termios) -> speed_t --- + + /* + Set the input baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html ]] + */ + cfsetispeed :: proc(termios_p: ^termios, rate: speed_t) -> result --- + + /* + Get the output baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html ]] + */ + cfgetospeed :: proc(termios_p: ^termios) -> speed_t --- + + /* + Set the output baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html ]] + */ + cfsetospeed :: proc(termios_p: ^termios, rate: speed_t) -> result --- + + /* + Wait for transmission of output. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html ]] + */ + tcdrain :: proc(fildes: FD) -> result --- + + /* + Suspend or restart the transmission or reception of data. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html ]] + */ + tcflow :: proc(fildes: FD, action: TC_Action) -> result --- + + /* + Flush non-transmitted output data, non-read input data, or both. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html ]] + */ + tcflush :: proc(fildes: FD, queue_selector: TC_Queue) -> result --- + + /* + Get the parameters associated with the terminal. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html ]] + */ + tcgetattr :: proc(fildes: FD, termios_p: ^termios) -> result --- + + /* + Get the process group ID for the session leader for the controlling terminal. + + Returns: -1 (setting errno) on failure, the pid otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html ]] + */ + tcgetsid :: proc(fildes: FD) -> pid_t --- + + /* + Send a break for a specific duration. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html ]] + */ + tcsendbreak :: proc(fildes: FD, duration: c.int) -> result --- + + /* + Set the parameters associated with the terminal. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html ]] + */ + tcsetattr :: proc(fildes: FD, optional_actions: TC_Optional_Action, termios_p: ^termios) -> result --- +} + +Control_Char :: enum c.int { + VEOF = VEOF, + VEOL = VEOL, + VERASE = VERASE, + VINTR = VINTR, + VKILL = VKILL, + VMIN = VMIN, + VQUIT = VQUIT, + VSTART = VSTART, + VSTOP = VSTOP, + VSUSP = VSUSP, + VTIME = VTIME, + + NCCS = NCCS-1, +} +#assert(len(#sparse [Control_Char]cc_t) == NCCS) + +CInput_Flag_Bits :: enum tcflag_t { + IGNBRK = log2(IGNBRK), /* ignore BREAK condition */ + BRKINT = log2(BRKINT), /* map BREAK to SIGINTR */ + IGNPAR = log2(IGNPAR), /* ignore (discard) parity errors */ + PARMRK = log2(PARMRK), /* mark parity and framing errors */ + INPCK = log2(INPCK), /* enable checking of parity errors */ + ISTRIP = log2(ISTRIP), /* strip 8th bit off chars */ + INLCR = log2(INLCR), /* map NL into CR */ + IGNCR = log2(IGNCR), /* ignore CR */ + ICRNL = log2(ICRNL), /* map CR to NL (ala CRMOD) */ + IXON = log2(IXON), /* enable output flow control */ + IXOFF = log2(IXOFF), /* enable input flow control */ + IXANY = log2(IXANY), /* any char will restart after stop */ +} +CInput_Flags :: bit_set[CInput_Flag_Bits; tcflag_t] + +CLocal_Flag_Bits :: enum tcflag_t { + ECHO = log2(ECHO), /* visual erase for line kill */ + ECHOE = log2(ECHOE), /* visually erase chars */ + ECHOK = log2(ECHOK), /* echo NL after line kill */ + ECHONL = log2(ECHONL), /* echo NL even if ECHO is off */ + ICANON = log2(ICANON), /* canonicalize input lines */ + IEXTEN = log2(IEXTEN), /* enable DISCARD and LNEXT */ + ISIG = log2(ISIG), /* enable signals INTR, QUIT, [D]SUSP */ + NOFLSH = log2(NOFLSH), /* don't flush after interrupt */ + TOSTOP = log2(TOSTOP), /* stop background jobs from output */ +} +CLocal_Flags :: bit_set[CLocal_Flag_Bits; tcflag_t] + +CControl_Flag_Bits :: enum tcflag_t { + // CS5 = log2(CS5), /* 5 bits (pseudo) (default) */ + CS6 = log2(CS6), /* 6 bits */ + CS7 = log2(CS7), /* 7 bits */ + CS8 = log2(CS8), /* 8 bits */ + CSTOPB = log2(CSTOPB), /* send 2 stop bits */ + CREAD = log2(CREAD), /* enable receiver */ + PARENB = log2(PARENB), /* parity enable */ + PARODD = log2(PARODD), /* odd parity, else even */ + HUPCL = log2(HUPCL), /* hang up on last close */ + CLOCAL = log2(CLOCAL), /* ignore modem status lines */ +} +CControl_Flags :: bit_set[CControl_Flag_Bits; tcflag_t] + +// character size mask +CSIZE :: CControl_Flags{ .CS6, .CS7, .CS8 } + +COutput_Flag_Bits :: enum tcflag_t { + OPOST = log2(OPOST), /* enable following output processing */ + ONLCR = log2(ONLCR), /* map NL to CR-NL (ala CRMOD) */ + OCRNL = log2(OCRNL), /* map CR to NL on output */ + ONOCR = log2(ONOCR), /* no CR output at column 0 */ + ONLRET = log2(ONLRET), /* NL performs CR function */ + OFDEL = log2(OFDEL), /* fill is DEL, else NUL */ + OFILL = log2(OFILL), /* use fill characters for delay */ + // NL0 = log2(NL0), /* \n delay 0 (default) */ + NL1 = log2(NL1), /* \n delay 1 */ + // CR0 = log2(CR0), /* \r delay 0 (default) */ + CR1 = log2(CR1), /* \r delay 1 */ + CR2 = log2(CR2), /* \r delay 2 */ + CR3 = log2(CR3), /* \r delay 3 */ + // TAB0 = log2(TAB0),/* horizontal tab delay 0 (default) */ + TAB1 = log2(TAB1), /* horizontal tab delay 1 */ + TAB3 = log2(TAB3), /* horizontal tab delay 3 */ + // BS0 = log2(BS0), /* \b delay 0 (default) */ + BS1 = log2(BS1), /* \b delay 1 */ + // VT0 = log2(VT0), /* vertical tab delay 0 (default) */ + VT1 = log2(VT1), /* vertical tab delay 1 */ + // FF0 = log2(FF0), /* form feed delay 0 (default) */ + FF1 = log2(FF1), /* form feed delay 1 */ +} +COutput_Flags :: bit_set[COutput_Flag_Bits; tcflag_t] + +// \n delay mask +NLDLY :: COutput_Flags{ .NL1, COutput_Flag_Bits(9) } +// \r delay mask +CRDLY :: COutput_Flags{ .CR1, .CR2, .CR3 } +// horizontal tab delay mask +TABDLY :: COutput_Flags{ .TAB1, .TAB3, COutput_Flag_Bits(2) } +// \b delay mask +BSDLY :: COutput_Flags{ .BS1 } +// vertical tab delay mask +VTDLY :: COutput_Flags{ .VT1 } +// form feed delay mask +FFDLY :: COutput_Flags{ .FF1 } + +speed_t :: enum _speed_t { + B0 = B0, + B50 = B50, + B75 = B75, + B110 = B110, + B134 = B134, + B150 = B150, + B200 = B200, + B300 = B300, + B600 = B600, + B1200 = B1200, + B1800 = B1800, + B2400 = B2400, + B4800 = B4800, + B9600 = B9600, + B19200 = B19200, + B38400 = B38400, +} + +TC_Action :: enum c.int { + TCIOFF = TCIOFF, + TCION = TCION, + TCOOFF = TCOOFF, + TCOON = TCOON, +} + +TC_Optional_Action :: enum c.int { + TCSANOW, + TCSADRAIN, + TCSAFLUSH, +} + +TC_Queue :: enum c.int { + TCIFLUSH = TCIFLUSH, + TCOFLUSH = TCOFLUSH, + TCIOFLUSH = TCIOFLUSH, +} + +when ODIN_OS == .Darwin { + + cc_t :: distinct c.uchar + _speed_t :: distinct c.ulong + tcflag_t :: distinct c.ulong + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 20 + + VEOF :: 0 + VEOL :: 1 + VERASE :: 3 + VINTR :: 8 + VKILL :: 5 + VMIN :: 16 + VQUIT :: 9 + VSTART :: 12 + VSTOP :: 13 + VSUSP :: 10 + VTIME :: 17 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000200 + IXOFF :: 0x00000400 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000002 + OCRNL :: 0x00000010 + ONOCR :: 0x00000020 + ONLRET :: 0x00000040 + OFDEL :: 0x00020000 + OFILL :: 0x00000080 + _NLDLY :: 0x00000300 + NL0 :: 0x00000000 + NL1 :: 0x00000100 + _CRDLY :: 0x00003000 + CR0 :: 0x00000000 + CR1 :: 0x00001000 + CR2 :: 0x00002000 + CR3 :: 0x00003000 + _TABDLY :: 0x00000c04 + TAB0 :: 0x00000000 + TAB1 :: 0x00000400 + TAB3 :: 0x00000800 + _BSDLY :: 0x00008000 + BS0 :: 0x00000000 + BS1 :: 0x00008000 + _VTDLY :: 0x00010000 + VT0 :: 0x00000000 + VT1 :: 0x00010000 + _FFDLY :: 0x00004000 + FF0 :: 0x00000000 + FF1 :: 0x00004000 + + B0 :: 0 + B50 :: 50 + B75 :: 75 + B110 :: 110 + B134 :: 134 + B150 :: 150 + B200 :: 200 + B300 :: 300 + B600 :: 600 + B1200 :: 1200 + B1800 :: 1800 + B2400 :: 2400 + B4800 :: 4800 + B9600 :: 9600 + B19200 :: 19200 + B38400 :: 38400 + + _CSIZE :: 0x00000300 + CS5 :: 0x00000000 + CS6 :: 0x00000100 + CS7 :: 0x00000200 + CS8 :: 0x00000300 + CSTOPB :: 0x00000400 + CREAD :: 0x00000800 + PARENB :: 0x00001000 + PARODD :: 0x00002000 + HUPCL :: 0x00004000 + CLOCAL :: 0x00008000 + + ECHO :: 0x00000008 + ECHOE :: 0x00000002 + ECHOK :: 0x00000004 + ECHONL :: 0x00000010 + ICANON :: 0x00000100 + IEXTEN :: 0x00000400 + ISIG :: 0x00000080 + NOFLSH :: 0x80000000 + TOSTOP :: 0x00400000 + + TCIFLUSH :: 1 + TCOFLUSH :: 2 + TCIOFLUSH :: 3 + + TCIOFF :: 3 + TCION :: 4 + TCOOFF :: 1 + TCOON :: 2 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + cc_t :: distinct c.uchar + _speed_t :: distinct c.uint + tcflag_t :: distinct c.uint + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 20 + + VEOF :: 0 + VEOL :: 1 + VERASE :: 3 + VINTR :: 8 + VKILL :: 5 + VMIN :: 16 + VQUIT :: 9 + VSTART :: 12 + VSTOP :: 13 + VSUSP :: 10 + VTIME :: 17 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000200 + IXOFF :: 0x00000400 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000002 + OCRNL :: 0x00000010 + when ODIN_OS == .OpenBSD { + ONOCR :: 0x00000040 + ONLRET :: 0x00000080 + } else { + ONOCR :: 0x00000020 + ONLRET :: 0x00000040 + } + OFDEL :: 0x00020000 // NOTE: not in headers + OFILL :: 0x00000080 // NOTE: not in headers + _NLDLY :: 0x00000300 // NOTE: not in headers + NL0 :: 0x00000000 // NOTE: not in headers + NL1 :: 0x00000100 // NOTE: not in headers + _CRDLY :: 0x00003000 // NOTE: not in headers + CR0 :: 0x00000000 // NOTE: not in headers + CR1 :: 0x00001000 // NOTE: not in headers + CR2 :: 0x00002000 // NOTE: not in headers + CR3 :: 0x00003000 // NOTE: not in headers + _TABDLY :: 0x00000004 // NOTE: not in headers (netbsd) + TAB0 :: 0x00000000 // NOTE: not in headers (netbsd) + TAB1 :: 0x00000004 // NOTE: not in headers + TAB3 :: 0x00000004 // NOTE: not in headers (netbsd) + _BSDLY :: 0x00008000 // NOTE: not in headers + BS0 :: 0x00000000 // NOTE: not in headers + BS1 :: 0x00008000 // NOTE: not in headers + _VTDLY :: 0x00010000 // NOTE: not in headers + VT0 :: 0x00000000 // NOTE: not in headers + VT1 :: 0x00010000 // NOTE: not in headers + _FFDLY :: 0x00004000 // NOTE: not in headers + FF0 :: 0x00000000 // NOTE: not in headers + FF1 :: 0x00004000 // NOTE: not in headers + + B0 :: 0 + B50 :: 50 + B75 :: 75 + B110 :: 110 + B134 :: 134 + B150 :: 150 + B200 :: 200 + B300 :: 300 + B600 :: 600 + B1200 :: 1200 + B1800 :: 1800 + B2400 :: 2400 + B4800 :: 4800 + B9600 :: 9600 + B19200 :: 19200 + B38400 :: 38400 + + _CSIZE :: 0x00000300 + CS5 :: 0x00000000 + CS6 :: 0x00000100 + CS7 :: 0x00000200 + CS8 :: 0x00000300 + CSTOPB :: 0x00000400 + CREAD :: 0x00000800 + PARENB :: 0x00001000 + PARODD :: 0x00002000 + HUPCL :: 0x00004000 + CLOCAL :: 0x00008000 + + ECHO :: 0x00000008 + ECHOE :: 0x00000002 + ECHOK :: 0x00000004 + ECHONL :: 0x00000010 + ICANON :: 0x00000100 + IEXTEN :: 0x00000400 + ISIG :: 0x00000080 + NOFLSH :: 0x80000000 + TOSTOP :: 0x00400000 + + TCIFLUSH :: 1 + TCOFLUSH :: 2 + TCIOFLUSH :: 3 + + TCIOFF :: 3 + TCION :: 4 + TCOOFF :: 1 + TCOON :: 2 + +} else when ODIN_OS == .Linux { + cc_t :: distinct c.uchar + _speed_t :: distinct c.uint + tcflag_t :: distinct c.uint + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_line: cc_t, /* control characters */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 32 + + VINTR :: 0 + VQUIT :: 1 + VERASE :: 2 + VKILL :: 3 + VEOF :: 4 + VTIME :: 5 + VMIN :: 6 + VSTART :: 8 + VSTOP :: 9 + VSUSP :: 10 + VEOL :: 11 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000400 + IXOFF :: 0x00001000 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000004 + OCRNL :: 0x00000008 + ONOCR :: 0x00000010 + ONLRET :: 0x00000020 + OFDEL :: 0x00000080 + OFILL :: 0x00000040 + _NLDLY :: 0x00000100 + NL0 :: 0x00000000 + NL1 :: 0x00000100 + _CRDLY :: 0x00000600 + CR0 :: 0x00000000 + CR1 :: 0x00000200 + CR2 :: 0x00000400 + CR3 :: 0x00000600 + _TABDLY :: 0x00001800 + TAB0 :: 0x00000000 + TAB1 :: 0x00000800 + TAB3 :: 0x00001800 + _BSDLY :: 0x00002000 + BS0 :: 0x00000000 + BS1 :: 0x00002000 + _VTDLY :: 0x00004000 + VT0 :: 0x00000000 + VT1 :: 0x00004000 + _FFDLY :: 0x00008000 + FF0 :: 0x00000000 + FF1 :: 0x00008000 + + B0 :: 0x00000000 + B50 :: 0x00000001 + B75 :: 0x00000002 + B110 :: 0x00000003 + B134 :: 0x00000004 + B150 :: 0x00000005 + B200 :: 0x00000006 + B300 :: 0x00000007 + B600 :: 0x00000008 + B1200 :: 0x00000009 + B1800 :: 0x0000000a + B2400 :: 0x0000000b + B4800 :: 0x0000000c + B9600 :: 0x0000000d + B19200 :: 0x0000000e + B38400 :: 0x0000000f + + _CSIZE :: 0x00000030 + CS5 :: 0x00000000 + CS6 :: 0x00000010 + CS7 :: 0x00000020 + CS8 :: 0x00000030 + CSTOPB :: 0x00000040 + CREAD :: 0x00000080 + PARENB :: 0x00000100 + PARODD :: 0x00000200 + HUPCL :: 0x00000400 + CLOCAL :: 0x00000800 + + ECHO :: 0x00000008 + ECHOE :: 0x00000010 + ECHOK :: 0x00000020 + ECHONL :: 0x00000040 + ICANON :: 0x00000002 + IEXTEN :: 0x00008000 + ISIG :: 0x00000001 + NOFLSH :: 0x80000080 + TOSTOP :: 0x00000100 + + TCIFLUSH :: 0 + TCOFLUSH :: 1 + TCIOFLUSH :: 2 + + TCIOFF :: 2 + TCION :: 3 + TCOOFF :: 0 + TCOON :: 1 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/time.odin b/core/sys/posix/time.odin new file mode 100644 index 000000000..5c6ebcf2f --- /dev/null +++ b/core/sys/posix/time.odin @@ -0,0 +1,234 @@ +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// time.h - time types + +foreign lib { + /* + Convert the broken down time in the structure to a string form: Sun Sep 16 01:03:52 1973. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/asctime_r.html ]] + */ + asctime_r :: proc(tm: ^tm, buf: [^]c.char) -> cstring --- + + /* + Convert a time value to a date and time string in the same format as asctime(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ctime_r.html ]] + */ + @(link_name=LCTIMER) + ctime_r :: proc(clock: ^time_t, buf: [^]c.char) -> cstring --- + + /* + Converts the time in seconds since epoch to a broken-down tm struct. + + Returns: nil (setting errno) on failure, the result pointer on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gmtime_r.html ]] + */ + @(link_name=LGMTIMER) + gmtime_r :: proc(timer: ^time_t, result: ^tm) -> ^tm --- + + /* + Convert the time in seconds since epoch to a broken-down tm struct in local time. + + Returns: nil (setting errno) on failure, the result pointer on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localtime_r.html ]] + */ + @(link_name=LLOCALTIMER) + localtime_r :: proc(timer: ^time_t, result: ^tm) -> ^tm --- + + /* + Returns the resolution of any clock. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKGETRES) + clock_getres :: proc(clock_id: Clock, res: ^timespec) -> result --- + + /* + Returns the current value tp for the specified clock, clock_id. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKGETTIME) + clock_gettime :: proc(clock_id: Clock, tp: ^timespec) -> result --- + + /* + Sets the specified clock's time. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKSETTIME) + clock_settime :: proc(clock_id: Clock, tp: ^timespec) -> result --- + + /* + Converts a string representation of a date or time into a broken-down time. + + Returns: nil (setting getdate_err) on failure, the broken-down time otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdate.html ]] + */ + getdate :: proc(string: cstring) -> ^tm --- + + /* + Causes the current thread to be suspended from execution until either the time interval + specified by rqtp has elapsed or a signal is delivered. + + Returns: -1 on failure (setting errno), if it was due to a signal, rmtp will be filled with the + remaining time, 0 if all time has been slept + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nanosleep.html ]] + */ + @(link_name=LNANOSLEEP) + nanosleep :: proc(rqtp: ^timespec, rmtp: ^timespec) -> result --- + + /* + Converts the character string to values which are stored in tm, using the specified format. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strptime.html ]] + */ + strptime :: proc(buf: [^]c.char, format: cstring, tm: ^tm) -> cstring --- + + /* + Uses the value of the environment variable TZ (or default) to set time conversion info. + + `daylight` is set to whether daylight saving time conversion should be done. + `timezone` is set to the difference, in seconds, between UTC and local standard time. + `tzname` is set by `tzname[0] = "std"` and `tzname[1] = "dst"` + + Example: + posix.tzset() + fmt.println(posix.tzname) + fmt.println(posix.daylight) + fmt.println(posix.timezone) + + Possible Output: + ["CET", "CEST"] + true + -3600 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tzset.html ]] + */ + tzset :: proc() --- + + // Whether daylight saving conversion should be done. + daylight: b32 + // The time in seconds between UTC and local standard time. + @(link_name=LTIMEZONE) + timezone: c.long + tzname: [2]cstring +} + +time_t :: libc.time_t +clock_t :: libc.clock_t + +tm :: libc.tm +timespec :: libc.timespec + +CLOCKS_PER_SEC :: libc.CLOCKS_PER_SEC + +asctime :: libc.asctime +clock :: libc.clock +ctime :: libc.ctime +difftime :: libc.difftime +gmtime :: libc.gmtime +localtime :: libc.localtime +mktime :: libc.mktime +strftime :: libc.strftime +time :: libc.time + +Clock :: enum clockid_t { + // system-wide monotonic clock, defined as clock measuring real time, + // can be set with clock_settime() and cannot have negative clock jumps. + MONOTONIC = CLOCK_MONOTONIC, + // CPU-time clock associated with the process making a clock() function call. + PROCESS_CPUTIME_ID = CLOCK_PROCESS_CPUTIME_ID, + // system-wide clock measuring real time. + REALTIME = CLOCK_REALTIME, + // CPU-time clock associated with the thread making a clock() function call. + THREAD_CPUTIME_ID = CLOCK_THREAD_CPUTIME_ID, +} + +when ODIN_OS == .NetBSD { + @(private) LCTIMER :: "__ctime_r50" + @(private) LGMTIMER :: "__gmtime_r50" + @(private) LLOCALTIMER :: "__localtime_r50" + @(private) LCLOCKGETRES :: "__clock_getres50" + @(private) LCLOCKGETTIME :: "__clock_gettime50" + @(private) LCLOCKSETTIME :: "__clock_settime50" + @(private) LNANOSLEEP :: "__nanosleep50" + @(private) LTIMEZONE :: "__timezone13" +} else { + @(private) LCTIMER :: "ctime_r" + @(private) LGMTIMER :: "gmtime_r" + @(private) LLOCALTIMER :: "localtime_r" + @(private) LCLOCKGETRES :: "clock_getres" + @(private) LCLOCKGETTIME :: "clock_gettime" + @(private) LCLOCKSETTIME :: "clock_settime" + @(private) LNANOSLEEP :: "nanosleep" + @(private) LTIMEZONE :: "timezone" +} + +when ODIN_OS == .Darwin { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 6 + CLOCK_PROCESS_CPUTIME_ID :: 12 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 16 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .FreeBSD { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 4 + CLOCK_PROCESS_CPUTIME_ID :: 15 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 14 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .NetBSD { + + clockid_t :: distinct c.uint + + CLOCK_MONOTONIC :: 3 + CLOCK_PROCESS_CPUTIME_ID :: 0x40000000 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 0x20000000 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .OpenBSD { + + clockid_t :: distinct c.uint + + CLOCK_MONOTONIC :: 3 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 4 + + getdate_err: Errno = .ENOSYS // NOTE: looks like it's not a thing on OpenBSD. + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/ulimit.odin b/core/sys/posix/ulimit.odin new file mode 100644 index 000000000..067b83271 --- /dev/null +++ b/core/sys/posix/ulimit.odin @@ -0,0 +1,43 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// ulimit.h - ulimit commands + +foreign lib { + /* + Control process limits. + + Note that -1 is a valid return value, applications should clear errno, do this call and then + check both -1 and the errno to determine status. + + Returns: -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ulimit.html ]] + */ + ulimit :: proc(i: c.int, #c_vararg arg: ..c.long) -> c.long --- +} + +Ulimit_Cmd :: enum c.int { + // Returns the file size limit of the process in units of 512-byte blocks inherited by children. + GETFSIZE = UL_GETFSIZE, + // Set the file size limit for output operations, taken as a long, multiplied by 512. + SETFSIZE = UL_SETFSIZE, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + UL_GETFSIZE :: 1 + UL_SETFSIZE :: 2 + + // NOTE: I don't think OpenBSD implements this API. + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin new file mode 100644 index 000000000..15dbb576f --- /dev/null +++ b/core/sys/posix/unistd.odin @@ -0,0 +1,1917 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// unistd.h - standard symbolic constants and types + +foreign lib { + /* + Checks the file named by the pathname pointed to by the path argument for + accessibility according to the bit pattern contained in amode. + + Example: + if (posix.access("/tmp/myfile", posix.F_OK) != .OK) { + fmt.printfln("/tmp/myfile access check failed: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] + */ + access :: proc(path: cstring, amode: Mode_Flags = F_OK) -> result --- + + /* + Equivalent to `access` but relative paths are resolved based on `fd`. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] + */ + faccessat :: proc(fd: FD, path: cstring, amode: Mode_Flags, flag: AT_Flags) -> result --- + + /* + The alarm() function shall cause the system to generate a SIGALRM signal for the process after the number of realtime seconds specified by seconds have elapsed. Processor scheduling delays may prevent the process from handling the signal as soon as it is generated. + + If seconds is 0, a pending alarm request, if any, is canceled. + + Returns: the time left on the previous alarm() or 0 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/alarm.html ]] + */ + alarm :: proc(seconds: c.uint) -> c.uint --- + + /* + Causes the directory named by path to become the current working directory. + + Example: + if (posix.chdir("/tmp") == .OK) { + fmt.println("changed current directory to /tmp") + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html ]] + */ + chdir :: proc(path: cstring) -> result --- + + /* + Equivalent to chdir but instead of a path the fildes is resolved to a directory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchdir.html ]] + */ + fchdir :: proc(fildes: FD) -> result --- + + /* + Changes the user and group ownership of a file. + + If owner or group is specified as (uid_t)-1 or (gid_t)-1, respectively, the corresponding ID of the file shall not be changed. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html ]] + */ + @(link_name=LCHOWN) + chown :: proc(path: cstring, owner: uid_t, group: gid_t) -> result --- + + /* + Equivalent to chown expect that it takes a file descriptor. + + Example: + fildes := posix.open("/home/cnd/mod1", {.RDWR}) + pwd := posix.getpwnam("jones") + grp := posix.getgrnam("cnd") + posix.fchown(fildes, pwd.pw_uid, grp.gr_gid) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html ]] + */ + @(link_name=LFCHOWN) + fchown :: proc(fildes: FD, owner: uid_t, mode: gid_t) -> result --- + + /* + Equivalent to fchown except that relative paths are based on the given fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html ]] + */ + fchownat :: proc(fildes: FD, path: cstring, owner: uid_t, group: gid_t, flag: AT_Flags) -> result --- + + /* + If path points to a symbolic link, the owner and group of the link itself is changed. + Equivalent to chown on normal files. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lchown.html ]] + */ + @(link_name=LLCHOWN) + lchown :: proc(path: cstring, owner: uid_t, group: gid_t) -> result --- + + /* + Deallocates the file descriptor indicated by fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html ]] + */ + close :: proc(fildes: FD) -> result --- + + /* + Return configuration-defined string values. + Its use and purpose are similar to sysconf(), but it is used where string values rather than numeric values are returned. + + Returns: 0 (setting errno) if `name` is invalid, need `buf` of `len` bytes if `buf` is `nil`, amount of bytes added to buf otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html ]] + */ + confstr :: proc(name: CS, buf: [^]c.char, len: c.size_t) -> c.size_t --- + + /* + Determines the current value of a configurable limit or option that is associated with a file or directory. + + Returns: value on success, -1 (setting errno) on failure, -1 (no errno) if the variable should be taken from limits + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fpathconf.html ]] + */ + pathconf :: proc(path: cstring, name: PC) -> c.long --- + + /* + Equivalent to pathconf but takes a file descriptor instead of a path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fpathconf.html ]] + */ + fpathconf :: proc(fildes: FD, name: PC) -> c.long --- + + /* + Determines the current value of configurable system limit or options. + + Returns: value on success, -1 (setting errno) on failure, -1 (no errno) if the variable should be taken from limits + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html ]] + */ + sysconf :: proc(name: SC) -> c.long --- + + /* + A string encoding function. The algorithm is implementation-defined. + + The use of crypt() for anything other than password hashing is not recommended. + + Returns: a static string overwritten by subsequent calls, `nil` (setting errno) on failure + */ + crypt :: proc(key: cstring, salt: cstring) -> cstring --- + + /* + An implementation-defined encoding algorithm. + The key generated by setkey() is used to encrypt the string block with encrypt(). + + block must be 64 bytes. + + decode controls if the block is encoded or decoded. + + May set errno to ENOSYS if the functionality is not supported. + + Example: + block: [64]byte + copy(block[:], "Hello, World!") + + posix.set_errno(.NONE) + posix.encrypt(raw_data(block[:]), decode=false) + assert(posix.errno() == .NONE, "encrypt not supported") + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/encrypt.html ]] + */ + encrypt :: proc(block: [^]c.char, decode: b32) --- + + /* + Returns a new file descriptor referring to the one given, sharing locks, clearing CLOEXEC. + + Returns: -1 (setting errno) on failure, the new file descriptor on success + + Example: + // Redirecting stdout to a file: + file := posix.open("/tmp/out", { .RDWR }) + posix.close(1) + posix.dup(file) + posix.close(file) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html ]] + */ + dup :: proc(fildes: FD) -> FD --- + + /* + Causes the file descriptor fildes2 to refer to the same open file description as + the file descriptor fildes and to share any locks, and shall return fildes2. + + Returns: -1 (setting errno) on failure, fildes2 on success + + Example: + // Redirecting stderr to stdout: + posix.dup2(1, 2) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html ]] + */ + dup2 :: proc(fildes, fildes2: FD) -> FD --- + + /* + Exits but, shall not call functions registered with atexit() nor any registered signal handlers. + Open streams shall not be flushed. + Whether open streams are closed (without flushing) is implementation-defined. Finally, the calling process shall be terminated with the consequences described below. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]] + */ + _exit :: proc(status: c.int) -> ! --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as varargs and the last of them must be nil. + + Example: + ret := posix.execl("/bin/ls", "ls", "-l", nil) + fmt.panicf("could not execute: %v %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execl :: proc(path: cstring, arg0: cstring, #c_vararg args: ..cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as varargs and the last of them must be nil. + After the arguments an array of environment strings (also nil terminated) is expected. + + Example: + env := []cstring{ + "HOME=/usr/home", + "LOGNAME=home", + nil, + } + ret := posix.execle("/bin/ls", "ls", cstring("-l"), cstring(nil), raw_data(env)) + fmt.panicf("could not execute: %v", posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execle :: proc(path: cstring, arg0: cstring, #c_vararg args: ..any) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + If file does not contain a slash the PATH environment variable is searched for a matching file. + Takes arguments as varargs and the last of them must be nil. + + Example: + ret := posix.execlp("ls", "-l", cstring(nil)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execlp :: proc(file: cstring, arg0: cstring, #c_vararg args: ..cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as an array which should be nil terminated. + + Example: + args := []cstring{ "ls", "-l", nil } + ret := posix.execv("/bin/ls", raw_data(args)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execv :: proc(path: cstring, argv: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + If file does not contain a slash the PATH environment variable is searched for a matching file. + Takes arguments as an array which should be nil terminated. + + Example: + cmd := []cstring{ "ls", "-l", nil } + ret := posix.execvp("ls", raw_data(cmd)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execvp :: proc(file: cstring, argv: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as an array which should be nil terminated. + Takes environment variables as an array which should be nil terminated. + + Example: + cmd := []cstring{ "ls", "-l", nil } + env := []cstring{ "HOME=/usr/home", "LOGNAME=home", nil } + ret := posix.execve("/bin/ls", raw_data(cmd), raw_data(env)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execve :: proc(path: cstring, argv: [^]cstring, envp: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Equivalent to execve but takes a file descriptor instead of a path. + + Example: + ls := posix.open("/bin/ls", { .EXEC }) + cmd := []cstring{ "ls", "-l", nil } + env := []cstring{ "HOME=/usr/home", "LOGNAME=home", nil } + ret := posix.fexecve(ls, raw_data(cmd), raw_data(env)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + fexecve :: proc(fd: FD, argv: [^]cstring, envp: [^]cstring) -> c.int --- + + /* + Example: + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + fmt.println(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + environ: [^]cstring + + /* + Forcec all currently queued I/O operations associated with the file indicated by file descriptor + fildes to the synchronized I/O completion state. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html ]] + */ + fdatasync :: proc(fd: FD) -> result --- + + /* + The fork() function shall create a new process. + The new process (child process) shall be an exact copy of the calling process (parent process). + With some exceptions outlined below. + + Result: -1 (setting errno) on failure, otherwise 0 to the child process and the child process id to the parent process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html ]] + */ + fork :: proc() -> pid_t --- + + /* + Requests that all data for the open file descriptor named by fildes is to be transferred + to the storage device associated with the file described by fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html ]] + */ + fsync :: proc(fildes: FD) -> result --- + + /* + Truncates a file to the specified length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html ]] + */ + truncate :: proc(path: cstring, length: off_t) -> result --- + + /* + Truncates a file to the specified length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html ]] + */ + ftruncate :: proc(fildes: FD, length: off_t) -> result --- + + /* + Places an absolute pathname of the current working directory into buf. + + Returns: buf as a cstring on success, nil (setting errno) on failure + + Example: + size: int + path_max := posix.pathconf(".", ._PATH_MAX) + if path_max == -1 { + size = 1024 + } else if path_max > 10240 { + size = 10240 + } else { + size = int(path_max) + } + + buf: [dynamic]byte + cwd: cstring + for ; cwd == nil; size *= 2 { + if err := resize(&buf, size); err != nil { + fmt.panicf("allocation failure: %v", err) + } + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if cwd == nil { + errno := posix.errno() + if errno != .ERANGE { + fmt.panicf("getcwd failure: %v", posix.strerror(errno)) + } + } + } + + fmt.println(path_max, cwd) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html ]] + */ + getcwd :: proc(buf: [^]c.char, size: c.size_t) -> cstring --- + + /* + Returns the effective group ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getegid.html ]] + */ + getegid :: proc() -> gid_t --- + + /* + Returns the effective user ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/geteuid.html ]] + */ + geteuid :: proc() -> uid_t --- + + /* + Returns the real group ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgid.html ]] + */ + getgid :: proc() -> gid_t --- + + /* + Fills the grouplist array with the current supplementary group IDs of the calling process. + + Returns: -1 (setting errno) on failure, desired grouplist length if gidsetsize is 0, amount of IDs added otherwise + + Example: + length := posix.getgroups(0, nil) + if length == -1 { + fmt.panicf("getgroups failure: %v", posix.strerror(posix.errno())) + } + + groups := make([]posix.gid_t, length) or_else panic("allocation failure") + if posix.getgroups(length, raw_data(groups)) != length { + fmt.panicf("getgroups failure: %v", posix.strerror(posix.errno())) + } + + fmt.println(groups) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgroups.html ]] + */ + getgroups :: proc(gidsetsize: c.int, grouplist: [^]gid_t) -> c.int --- + + /* + Retrieves a 32-bit identifier for the current host. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostid.html ]] + */ + gethostid :: proc() -> c.long --- + + /* + Returns the standard host name for the current machine. + + Host names are limited to HOST_NAME_MAX bytes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html ]] + */ + gethostname :: proc(name: [^]c.char, namelen: c.size_t) -> result --- + + /* + Returns a string containing the user name associated by the login activity. + + Returns: nil (setting errno) on failure, the login name otherwise in a potentially static buffer overwritten by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getlogin.html ]] + */ + getlogin :: proc() -> cstring --- + + /* + Equivalent to getlogin but puts the name in the name buffer given. + + The name is limited to LOGIN_NAME_MAX bytes. + + Example: + max := posix.sysconf(posix._SC_LOGIN_NAME_MAX)+1 + buf := make([]byte, max) + posix.getlogin_r(raw_data(buf), uint(len(max))) + fmt.printfln("login: %v", cstring(buf)) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getlogin.html ]] + */ + getlogin_r :: proc(name: [^]c.char, namelen: c.size_t) -> Errno --- + + /* + A command-line parser, see linked docs. + + Example: + // The following code fragment shows how you might process the arguments for a utility that + // can take the mutually-exclusive options a and b and the options f and o, both of which + // require arguments. + + bflg, aflg, errflg: bool + ifile: string + ofile: string + + for { + c := posix.getopt(i32(len(runtime.args__)), raw_data(runtime.args__), ":abf:o:") + (c != -1) or_break + + switch c { + case 'a': + if bflg { + errflg = true + } else { + aflg = true + } + case 'b': + if aflg { + errflg = true + } else { + bflg = true + } + case 'f': + ifile = string(posix.optarg) + case 'o': + ofile = string(posix.optarg) + case ':': /* -f or -o without operand */ + fmt.eprintfln("Option -%c requires an operand", posix.optopt) + errflg = true + case '?': + fmt.eprintfln("Unrecognized option: '-%c'", posix.optopt) + errflg = true + } + } + + if errflg { + fmt.eprintfln("usage: . . . ") + posix.exit(2) + } + + // Loop through remaining arguments: + for ; posix.optind < i32(len(runtime.args__)); posix.optind += 1 { + fmt.println(runtime.args__[posix.optind]) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html ]] + */ + getopt :: proc(argc: c.int, argv: [^]cstring, optstring: cstring) -> c.int --- + + optarg: cstring + opterr: c.int + optind: c.int + optopt: c.int + + /* + Returns the process group ID of the process whose process ID is equal to pid. + If pid is 0, it returns the process group ID of the calling process. + + Returns: -1 on failure, the ID otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgid.html ]] + */ + getpgid :: proc(pid: pid_t) -> pid_t --- + + /* + Returns the process group ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgrp.html ]] + */ + getpgrp :: proc() -> pid_t --- + + /* + Returns the ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpid.html ]] + */ + getpid :: proc() -> pid_t --- + + /* + Returns the parent process ID. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getppid.html ]] + */ + getppid :: proc() -> pid_t --- + + + /* + Get the process group ID of the session leader. + If pid is 0, it is the current process. + + Returns: -1 (setting errno) on failure, the pid otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsid.html ]] + */ + getsid :: proc(pid: pid_t) -> pid_t --- + + /* + Returns the real user ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getuid.html ]] + */ + getuid :: proc() -> uid_t --- + + /* + Tests whether fildes is associated with a terminal device. + + Returns: false (setting errno) if fildes is invalid or not a terminal, true otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/isatty.html ]] + */ + isatty :: proc(fildes: FD) -> b32 --- + + /* + Creates a new link for the existing file path1 to path2. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html ]] + */ + link :: proc(path1: cstring, path2: cstring) -> result --- + + /* + If path1 is relative it is relative to directory fd1. + If path2 is relative it is relative to directory fd2. + If flag is { .SYMLINK_FOLLOW } path1 is resolved to its link if it is a link. + Equivalent to link otherwise. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html ]] + */ + linkat :: proc(fd1: FD, path1: cstring, fd2: FD, path2: cstring, flag: AT_Flags) -> result --- + + /* + Creates a symbolic link called path2 that contains a link to path1. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html ]] + */ + symlink :: proc(path1: cstring, path2: cstring) -> result --- + + /* + Equivalent to symlink but relative paths are resolved to dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html ]] + */ + symlinkat :: proc(path1: cstring, fd: FD, path2: cstring) -> result --- + + /* + Locks sections of a file with advisory-mode locks. + + Example: + fildes := posix.open("/home/cnd/mod1", { .RDWR }) + if posix.lockf(fildes, .TLOCK, 10000) != .OK { + errno := posix.errno(); #partial switch errno { + case .EACCES, .EAGAIN: + // File is already locked. + case: + // Other error. + fmt.panicf("lockf failure: %v", posix.strerror(errno)) + } + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lockf.html ]] + */ + lockf :: proc(fildes: FD, function: Lock_Function, size: off_t) -> result --- + + /* + Sets the file offset of the given file descriptor. + + If whence is .SET, the offset is set + If whence is .CUR, the offset is the current offset + given offset + If whence is .END, the offset is set to the size of the file + given offset + + Returns: the resulting offset or -1 (setting errno) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html ]] + */ + lseek :: proc(fildes: FD, offset: off_t, whence: Whence) -> off_t --- + + /* + Changes the nice value of a process. + + Higher values result in less favorable scheduling. + + Because -1 is a valid nice value, checking failure would be done by first setting errno to .NONE + and then calling nice. + + Returns: the new nice value, or -1 (setting) errno on failure + + Example: + posix.set_errno(.NONE) + niceness := posix.nice(-20) + if errno := posix.errno(); niceness == -1 && errno != .NONE { + fmt.panicf("nice failure: %v", posix.strerror(errno)) + } + fmt.printfln("Niceness is now: %v", niceness) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nice.html ]] + */ + nice :: proc(incr: c.int) -> c.int --- + + /* + Suspend the thread until a signal is received. + + Returns: -1 (setting errno to EINTR) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html ]] + */ + pause :: proc() -> c.int --- + + /* + Create an interprocess channel. + + Example: + fildes: [2]posix.FD + if posix.pipe(&fildes) != .OK { + // Handle error ... + } + + switch posix.fork() { + case -1: + // Handle error ... + + case 0: /* Child - reads from pipe */ + BSIZE :: 100 + buf: [BSIZE]byte + nbytes: int + + posix.close(fildes[1]) /* Write end is unused */ + nbytes = posix.read(fildes[0], raw_data(buf[:]), BSIZE) /* Get data from pipe */ + /* At this point, a further read would see end-of-file ... */ + posix.close(fildes[0]) /* Finished with pipe */ + + fmt.println(string(buf[:nbytes])) + + posix.exit(0) + + case: /* Parent - write to pipe */ + msg := raw_data(transmute([]byte)string("Hello world\n")) + posix.close(fildes[0]) /* Read end is unused */ + posix.write(fildes[1], msg, 12); /* Write data on pipe */ + posix.close(fildes[1]) + posix.exit(0) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html ]] + */ + pipe :: proc(fildes: ^[2]FD) -> result --- + + /* + Read from a file. + + Returns: the amount of bytes read or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html ]] + */ + read :: proc(fd: FD, buf: [^]byte, nbyte: c.size_t) -> c.ssize_t --- + + /* + Equivalent to read on a specified offset instead of the internal offset. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html ]] + */ + pread :: proc(fd: FD, buf: [^]byte, nbyte: c.size_t, offset: off_t) -> c.ssize_t --- + + /* + Write on a file. + + Returns: the amount of bytes written or -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html ]] + */ + write :: proc(fd: FD, buf: [^]byte, buflen: c.size_t) -> c.ssize_t --- + + /* + Equivalent to write on a specified offset instead of the internal offset. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html ]] + */ + pwrite :: proc(fd: FD, buf: [^]byte, buflen: c.size_t, offset: off_t) -> c.ssize_t --- + + /* + Read the contents of a symbolic link. + + Returns: the amount of bytes read or -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html ]] + */ + readlink :: proc(path: cstring, buf: [^]byte, bufsize: c.size_t) -> c.ssize_t --- + + /* + Equivalent to readlink but relative paths are resolved based on the dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html ]] + */ + readlinkat :: proc(fd: FD, path: cstring, buf: [^]byte, bufsize: c.size_t) -> c.ssize_t --- + + /* + Remove an (empty) directory. + + ]] More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html ]] + */ + rmdir :: proc(path: cstring) -> result --- + + /* + Set the effective group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setegid.html ]] + */ + setegid :: proc(gid: gid_t) -> result --- + + /* + Sets the effective user ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/seteuid.html ]] + */ + seteuid :: proc(uid: uid_t) -> result --- + + /* + Sets the group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setgid.html ]] + */ + setgid :: proc(gid: gid_t) -> result --- + + /* + Set process group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgid.html ]] + */ + setpgid :: proc(pid: pid_t, pgid: pid_t) -> result --- + + /* + Set the process group ID to that of the process. + + Returns: the process group id, no failures are defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgrp.html ]] + */ + setpgrp :: proc() -> pid_t --- + + /* + Set the real and effective group IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setregid.html ]] + */ + setregid :: proc(rgid: gid_t, egid: gid_t) -> result --- + + /* + Set real and effective user IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setreuid.html ]] + */ + setreuid :: proc(ruid: uid_t, euid: uid_t) -> result --- + + /* + Create session and set process group ID. + + Returns: the new process group ID or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsid.html ]] + */ + setsid :: proc() -> pid_t --- + + /* + Set user ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setuid.html ]] + */ + setuid :: proc(uid: uid_t) -> result --- + + /* + Suspend execution for an interval of time. + + Returns: the time left to sleep (may be > 0 in case of signals) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sleep.html ]] + */ + sleep :: proc(seconds: c.uint) -> c.uint --- + + /* + Copy nbyte bytes, from src, to dest, exchanging adjecent bytes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/swab.html ]] + */ + swab :: proc(src: [^]byte, dest: [^]byte, nbytes: c.ssize_t) --- + + /* + Schedule file system updates. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sync.html ]] + */ + sync :: proc() --- + + /* + Get the foreground process group ID. + + Returns: -1 (setting errno) on failure, the id otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetpgrp.html ]] + */ + tcgetpgrp :: proc(fildes: FD) -> pid_t --- + + /* + Set the foreground process group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetpgrp.html ]] + */ + tcsetpgrp :: proc(fildes: FD, pgid_id: pid_t) -> result --- + + /* + Find the path name of a terminal. + + Returns: nil (setting errno) on failure, the name, which may be invalidated by subsequent calls on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ttyname.html ]] + */ + ttyname :: proc(fildes: FD) -> cstring --- + + /* + Equivalent to ttyname but name is placed into the buf. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ttyname.html ]] + */ + ttyname_r :: proc(fildes: FD, name: [^]byte, namesize: c.size_t) -> Errno --- + + /* + Remove a directory entry. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] + */ + unlink :: proc(path: cstring) -> result --- + + /* + Equivalent to unlink or rmdir (if flag is .REMOVEDIR) but relative paths are relative to the dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] + */ + unlinkat :: proc(fd: FD, path: cstring, flag: AT_Flags) -> result --- +} + +STDERR_FILENO :: 2 +STDIN_FILENO :: 0 +STDOUT_FILENO :: 1 + +Mode_Flag_Bits :: enum c.int { + X_OK = log2(X_OK), + W_OK = log2(W_OK), + R_OK = log2(R_OK), +} +Mode_Flags :: bit_set[Mode_Flag_Bits; c.int] + +#assert(_F_OK == 0) +F_OK :: Mode_Flags{} + +CS :: enum c.int { + _PATH = _CS_PATH, + _POSIX_V6_ILP32_OFF32_CFLAGS = _CS_POSIX_V6_ILP32_OFF32_CFLAGS, + _POSIX_V6_ILP32_OFF32_LDFLAGS = _CS_POSIX_V6_ILP32_OFF32_LDFLAGS, + _POSIX_V6_ILP32_OFF32_LIBS = _CS_POSIX_V6_ILP32_OFF32_LIBS, + _POSIX_V6_ILP32_OFFBIG_CFLAGS = _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS, + _POSIX_V6_ILP32_OFFBIG_LDFLAGS = _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS, + _POSIX_V6_ILP32_OFFBIG_LIBS = _CS_POSIX_V6_ILP32_OFFBIG_LIBS, + _POSIX_V6_LP64_OFF64_CFLAGS = _CS_POSIX_V6_LP64_OFF64_CFLAGS, + _POSIX_V6_LP64_OFF64_LDFLAGS = _CS_POSIX_V6_LP64_OFF64_LDFLAGS, + _POSIX_V6_LP64_OFF64_LIBS = _CS_POSIX_V6_LP64_OFF64_LIBS, + _POSIX_V6_LPBIG_OFFBIG_CFLAGS = _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS, + _POSIX_V6_LPBIG_OFFBIG_LDFLAGS = _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS, + _POSIX_V6_LPBIG_OFFBIG_LIBS = _CS_POSIX_V6_LPBIG_OFFBIG_LIBS, + _POSIX_V6_WIDTH_RESTRICTED_ENVS = _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS, +} + +PC :: enum c.int { + _2_SYMLINK = _PC_2_SYMLINK, + _ALLOC_SIZE_MIN = _PC_ALLOC_SIZE_MIN, + _ASYNC_IO = _PC_ASYNC_IO, + _CHOWN_RESTRICTED = _PC_CHOWN_RESTRICTED, + _FILESIZEBITS = _PC_FILESIZEBITS, + _LINK_MAX = _PC_LINK_MAX, + _MAX_CANON = _PC_MAX_CANON, + _MAX_INPUT = _PC_MAX_INPUT, + _NAME_MAX = _PC_NAME_MAX, + _NO_TRUNC = _PC_NO_TRUNC, + _PATH_MAX = _PC_PATH_MAX, + _PIPE_BUF = _PC_PIPE_BUF, + _PRIO_IO = _PC_PRIO_IO, + _REC_INCR_XFER_SIZE = _PC_REC_INCR_XFER_SIZE, + _REC_MAX_XFER_SIZE = _PC_REC_MAX_XFER_SIZE, + _REC_MIN_XFER_SIZE = _PC_REC_MIN_XFER_SIZE, + _REC_XFER_ALIGN = _PC_REC_XFER_ALIGN, + _SYMLINK_MAX = _PC_SYMLINK_MAX, + _SYNC_IO = _PC_SYNC_IO, + _VDISABLE = _PC_VDISABLE, +} + +SC :: enum c.int { + _2_C_BIND = _SC_2_C_BIND, + _2_C_DEV = _SC_2_C_DEV, + _2_CHAR_TERM = _SC_2_CHAR_TERM, + _2_FORT_DEV = _SC_2_FORT_DEV, + _2_FORT_RUN = _SC_2_FORT_RUN, + _2_LOCALEDEF = _SC_2_LOCALEDEF, + _2_PBS = _SC_2_PBS, + _2_PBS_ACCOUNTING = _SC_2_PBS_ACCOUNTING, + _2_PBS_CHECKPOINT = _SC_2_PBS_CHECKPOINT, + _2_PBS_LOCATE = _SC_2_PBS_LOCATE, + _2_PBS_MESSAGE = _SC_2_PBS_MESSAGE, + _2_PBS_TRACK = _SC_2_PBS_TRACK, + _2_SW_DEV = _SC_2_SW_DEV, + _2_UPE = _SC_2_UPE, + _2_VERSION = _SC_2_VERSION, + _ADVISORY_INFO = _SC_ADVISORY_INFO, + _AIO_LISTIO_MAX = _SC_AIO_LISTIO_MAX, + _AIO_MAX = _SC_AIO_MAX, + _AIO_PRIO_DELTA_MAX = _SC_AIO_PRIO_DELTA_MAX, + _ARG_MAX = _SC_ARG_MAX, + _ASYNCHRONOUS_IO = _SC_ASYNCHRONOUS_IO, + _ATEXIT_MAX = _SC_ATEXIT_MAX, + _BARRIERS = _SC_BARRIERS, + _BC_BASE_MAX = _SC_BC_BASE_MAX, + _BC_DIM_MAX = _SC_BC_DIM_MAX, + _BC_SCALE_MAX = _SC_BC_SCALE_MAX, + _BC_STRING_MAX = _SC_BC_STRING_MAX, + _CHILD_MAX = _SC_CHILD_MAX, + _CLK_TCK = _SC_CLK_TCK, + _CLOCK_SELECTION = _SC_CLOCK_SELECTION, + _COLL_WEIGHTS_MAX = _SC_COLL_WEIGHTS_MAX, + _CPUTIME = _SC_CPUTIME, + _DELAYTIMER_MAX = _SC_DELAYTIMER_MAX, + _EXPR_NEST_MAX = _SC_EXPR_NEST_MAX, + _FSYNC = _SC_FSYNC, + _GETGR_R_SIZE_MAX = _SC_GETGR_R_SIZE_MAX, + _GETPW_R_SIZE_MAX = _SC_GETPW_R_SIZE_MAX, + _HOST_NAME_MAX = _SC_HOST_NAME_MAX, + _IOV_MAX = _SC_IOV_MAX, + _IPV6 = _SC_IPV6, + _JOB_CONTROL = _SC_JOB_CONTROL, + _LINE_MAX = _SC_LINE_MAX, + _LOGIN_NAME_MAX = _SC_LOGIN_NAME_MAX, + _MAPPED_FILES = _SC_MAPPED_FILES, + _MEMLOCK = _SC_MEMLOCK, + _MEMLOCK_RANGE = _SC_MEMLOCK_RANGE, + _MEMORY_PROTECTION = _SC_MEMORY_PROTECTION, + _MESSAGE_PASSING = _SC_MESSAGE_PASSING, + _MONOTONIC_CLOCK = _SC_MONOTONIC_CLOCK, + _MQ_OPEN_MAX = _SC_MQ_OPEN_MAX, + _MQ_PRIO_MAX = _SC_MQ_PRIO_MAX, + _NGROUPS_MAX = _SC_NGROUPS_MAX, + _OPEN_MAX = _SC_OPEN_MAX, + _PAGE_SIZE = _SC_PAGE_SIZE, + _PAGESIZE = _SC_PAGESIZE, + _PRIORITIZED_IO = _SC_PRIORITIZED_IO, + _PRIORITY_SCHEDULING = _SC_PRIORITY_SCHEDULING, + _RAW_SOCKETS = _SC_RAW_SOCKETS, + _RE_DUP_MAX = _SC_RE_DUP_MAX, + _READER_WRITER_LOCKS = _SC_READER_WRITER_LOCKS, + _REALTIME_SIGNALS = _SC_REALTIME_SIGNALS, + _REGEXP = _SC_REGEXP, + _RTSIG_MAX = _SC_RTSIG_MAX, + _SAVED_IDS = _SC_SAVED_IDS, + _SEM_NSEMS_MAX = _SC_SEM_NSEMS_MAX, + _SEM_VALUE_MAX = _SC_SEM_VALUE_MAX, + _SEMAPHORES = _SC_SEMAPHORES, + _SHARED_MEMORY_OBJECTS = _SC_SHARED_MEMORY_OBJECTS, + _SHELL = _SC_SHELL, + _SIGQUEUE_MAX = _SC_SIGQUEUE_MAX, + _SPAWN = _SC_SPAWN, + _SPIN_LOCKS = _SC_SPIN_LOCKS, + _SPORADIC_SERVER = _SC_SPORADIC_SERVER, + _SS_REPL_MAX = _SC_SS_REPL_MAX, + _STREAM_MAX = _SC_STREAM_MAX, + _SYMLOOP_MAX = _SC_SYMLOOP_MAX, + _SYNCHRONIZED_IO = _SC_SYNCHRONIZED_IO, + _THREAD_ATTR_STACKADDR = _SC_THREAD_ATTR_STACKADDR, + _THREAD_ATTR_STACKSIZE = _SC_THREAD_ATTR_STACKSIZE, + _THREAD_CPUTIME = _SC_THREAD_CPUTIME, + _THREAD_DESTRUCTOR_ITERATIONS = _SC_THREAD_DESTRUCTOR_ITERATIONS, + _THREAD_KEYS_MAX = _SC_THREAD_KEYS_MAX, + _THREAD_PRIO_INHERIT = _SC_THREAD_PRIO_INHERIT, + _THREAD_PRIO_PROTECT = _SC_THREAD_PRIO_PROTECT, + _THREAD_PRIORITY_SCHEDULING = _SC_THREAD_PRIORITY_SCHEDULING, + _THREAD_PROCESS_SHARED = _SC_THREAD_PROCESS_SHARED, + _THREAD_SAFE_FUNCTIONS = _SC_THREAD_SAFE_FUNCTIONS, + _THREAD_SPORADIC_SERVER = _SC_THREAD_SPORADIC_SERVER, + _THREAD_STACK_MIN = _SC_THREAD_STACK_MIN, + _THREAD_THREADS_MAX = _SC_THREAD_THREADS_MAX, + _THREADS = _SC_THREADS, + _TIMEOUTS = _SC_TIMEOUTS, + _TIMER_MAX = _SC_TIMER_MAX, + _TIMERS = _SC_TIMERS, + _TRACE = _SC_TRACE, + _TRACE_EVENT_FILTER = _SC_TRACE_EVENT_FILTER, + _TRACE_EVENT_NAME_MAX = _SC_TRACE_EVENT_NAME_MAX, + _TRACE_INHERIT = _SC_TRACE_INHERIT, + _TRACE_LOG = _SC_TRACE_LOG, + _TRACE_NAME_MAX = _SC_TRACE_NAME_MAX, + _TRACE_SYS_MAX = _SC_TRACE_SYS_MAX, + _TRACE_USER_EVENT_MAX = _SC_TRACE_USER_EVENT_MAX, + _TTY_NAME_MAX = _SC_TTY_NAME_MAX, + _TYPED_MEMORY_OBJECTS = _SC_TYPED_MEMORY_OBJECTS, + _TZNAME_MAX = _SC_TZNAME_MAX, + _V6_ILP32_OFF32 = _SC_V6_ILP32_OFF32, + _V6_ILP32_OFFBIG = _SC_V6_ILP32_OFFBIG, + _V6_LP64_OFF64 = _SC_V6_LP64_OFF64, + _V6_LPBIG_OFFBIG = _SC_V6_LPBIG_OFFBIG, + _VERSION = _SC_VERSION, + _XOPEN_CRYPT = _SC_XOPEN_CRYPT, + _XOPEN_ENH_I18N = _SC_XOPEN_ENH_I18N, + _XOPEN_REALTIME = _SC_XOPEN_REALTIME, + _XOPEN_REALTIME_THREADS = _SC_XOPEN_REALTIME_THREADS, + _XOPEN_SHM = _SC_XOPEN_SHM, + _XOPEN_STREAMS = _SC_XOPEN_STREAMS, + _XOPEN_UNIX = _SC_XOPEN_UNIX, + _XOPEN_VERSION = _SC_XOPEN_VERSION, +} + +Lock_Function :: enum c.int { + // Lock a section for exclusive use. + LOCK = F_LOCK, + // Test a section for locks by other processes. + TEST = F_TEST, + // Test and lock a section for exclusive use. + TLOCK = F_TLOCK, + // Unlock locked sections. + ULOCK = F_ULOCK, +} + +when ODIN_OS == .NetBSD { + @(private) LCHOWN :: "__posix_chown" + @(private) LFCHOWN :: "__posix_fchown" + @(private) LLCHOWN :: "__posix_lchown" +} else { + @(private) LCHOWN :: "chown" + @(private) LFCHOWN :: "fchown" + @(private) LLCHOWN :: "lchown" +} + +when ODIN_OS == .Darwin { + + _F_OK :: 0 + X_OK :: (1<<0) + W_OK :: (1<<1) + R_OK :: (1<<2) + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 15 + _PC_ALLOC_SIZE_MIN :: 16 + _PC_ASYNC_IO :: 17 + _PC_FILESIZEBITS :: 18 + _PC_PRIO_IO :: 19 + _PC_REC_INCR_XFER_SIZE :: 20 + _PC_REC_MAX_XFER_SIZE :: 21 + _PC_REC_MIN_XFER_SIZE :: 22 + _PC_REC_XFER_ALIGN :: 23 + _PC_SYMLINK_MAX :: 24 + _PC_SYNC_IO :: 25 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_ASYNCHRONOUS_IO :: 28 + _SC_PAGE_SIZE :: 29 + _SC_PAGESIZE :: _SC_PAGE_SIZE + + _SC_MEMLOCK :: 30 + _SC_MEMLOCK_RANGE :: 31 + _SC_MEMORY_PROTECTION :: 32 + _SC_MESSAGE_PASSING :: 33 + _SC_PRIORITIZED_IO :: 34 + _SC_PRIORITY_SCHEDULING :: 35 + _SC_REALTIME_SIGNALS :: 36 + _SC_SEMAPHORES :: 37 + _SC_FSYNC :: 38 + _SC_SHARED_MEMORY_OBJECTS :: 39 + + _SC_SYNCHRONIZED_IO :: 40 + _SC_TIMERS :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_DELAYTIMER_MAX :: 45 + _SC_MQ_OPEN_MAX :: 46 + _SC_MAPPED_FILES :: 47 + _SC_RTSIG_MAX :: 48 + _SC_SEM_NSEMS_MAX :: 49 + + _SC_SEM_VALUE_MAX :: 50 + _SC_SIGQUEUE_MAX :: 51 + _SC_TIMER_MAX :: 52 + _SC_IOV_MAX :: 56 + _SC_2_PBS :: 59 + + _SC_2_PBS_ACCOUNTING :: 60 + _SC_2_PBS_CHECKPOINT :: 61 + _SC_2_PBS_LOCATE :: 62 + _SC_2_PBS_MESSAGE :: 63 + _SC_2_PBS_TRACK :: 64 + _SC_ADVISORY_INFO :: 65 + _SC_BARRIERS :: 66 + _SC_CLOCK_SELECTION :: 67 + _SC_CPUTIME :: 68 + + _SC_GETGR_R_SIZE_MAX :: 70 + _SC_GETPW_R_SIZE_MAX :: 71 + _SC_HOST_NAME_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_MONOTONIC_CLOCK :: 74 + _SC_MQ_PRIO_MAX :: 75 + _SC_READER_WRITER_LOCKS :: 76 + _SC_REGEXP :: 77 + _SC_SHELL :: 78 + _SC_SPAWN :: 79 + + _SC_SPIN_LOCKS :: 80 + _SC_SPORADIC_SERVER :: 81 + _SC_THREAD_ATTR_STACKADDR :: 82 + _SC_THREAD_ATTR_STACKSIZE :: 83 + _SC_THREAD_CPUTIME :: 84 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 85 + _SC_THREAD_KEYS_MAX :: 86 + _SC_THREAD_PRIO_INHERIT :: 87 + _SC_THREAD_PRIO_PROTECT :: 88 + _SC_THREAD_PRIORITY_SCHEDULING :: 89 + + _SC_THREAD_PROCESS_SHARED :: 90 + _SC_THREAD_SAFE_FUNCTIONS :: 91 + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_THREAD_STACK_MIN :: 93 + _SC_THREAD_THREADS_MAX :: 94 + _SC_TIMEOUTS :: 95 + _SC_THREADS :: 96 + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + + _SC_TRACE_LOG :: 100 + _SC_TTY_NAME_MAX :: 101 + _SC_TYPED_MEMORY_OBJECTS :: 102 + _SC_V6_ILP32_OFF32 :: 103 + _SC_V6_ILP32_OFFBIG :: 104 + _SC_V6_LP64_OFF64 :: 105 + _SC_V6_LPBIG_OFFBIG :: 106 + _SC_ATEXIT_MAX :: 107 + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_SHM :: 113 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_SYMLOOP_MAX :: 120 + _SC_SS_REPL_MAX :: 126 + _SC_TRACE_EVENT_NAME_MAX :: 127 + _SC_TRACE_NAME_MAX :: 128 + _SC_TRACE_SYS_MAX :: 129 + _SC_TRACE_USER_EVENT_MAX :: 130 + + _POSIX_VDISABLE :: '\377' + +} else when ODIN_OS == .FreeBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 13 // NOTE: not in headers (freebsd) + _PC_ALLOC_SIZE_MIN :: 10 + _PC_ASYNC_IO :: 53 + _PC_FILESIZEBITS :: 12 + _PC_PRIO_IO :: 54 + _PC_REC_INCR_XFER_SIZE :: 14 + _PC_REC_MAX_XFER_SIZE :: 15 + _PC_REC_MIN_XFER_SIZE :: 16 + _PC_REC_XFER_ALIGN :: 17 + _PC_SYMLINK_MAX :: 18 + _PC_SYNC_IO :: 55 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_ASYNCHRONOUS_IO :: 28 + _SC_MAPPED_FILES :: 29 + + _SC_MEMLOCK :: 30 + _SC_MEMLOCK_RANGE :: 31 + _SC_MEMORY_PROTECTION :: 32 + _SC_MESSAGE_PASSING :: 33 + _SC_PRIORITIZED_IO :: 34 + _SC_PRIORITY_SCHEDULING :: 35 + _SC_REALTIME_SIGNALS :: 36 + _SC_SEMAPHORES :: 37 + _SC_FSYNC :: 38 + _SC_SHARED_MEMORY_OBJECTS :: 39 + + _SC_SYNCHRONIZED_IO :: 40 + _SC_TIMERS :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_DELAYTIMER_MAX :: 45 + _SC_MQ_OPEN_MAX :: 46 + _SC_PAGE_SIZE :: 47 + _SC_PAGESIZE :: _SC_PAGE_SIZE + _SC_RTSIG_MAX :: 48 + _SC_SEM_NSEMS_MAX :: 49 + + _SC_SEM_VALUE_MAX :: 50 + _SC_SIGQUEUE_MAX :: 51 + _SC_TIMER_MAX :: 52 + _SC_IOV_MAX :: 56 + _SC_2_PBS :: 59 + + _SC_2_PBS_ACCOUNTING :: 60 + _SC_2_PBS_CHECKPOINT :: 61 + _SC_2_PBS_LOCATE :: 62 + _SC_2_PBS_MESSAGE :: 63 + _SC_2_PBS_TRACK :: 64 + _SC_ADVISORY_INFO :: 65 + _SC_BARRIERS :: 66 + _SC_CLOCK_SELECTION :: 67 + _SC_CPUTIME :: 68 + + _SC_GETGR_R_SIZE_MAX :: 70 + _SC_GETPW_R_SIZE_MAX :: 71 + _SC_HOST_NAME_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_MONOTONIC_CLOCK :: 74 + _SC_MQ_PRIO_MAX :: 75 + _SC_READER_WRITER_LOCKS :: 76 + _SC_REGEXP :: 77 + _SC_SHELL :: 78 + _SC_SPAWN :: 79 + + _SC_SPIN_LOCKS :: 80 + _SC_SPORADIC_SERVER :: 81 + _SC_THREAD_ATTR_STACKADDR :: 82 + _SC_THREAD_ATTR_STACKSIZE :: 83 + _SC_THREAD_CPUTIME :: 84 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 85 + _SC_THREAD_KEYS_MAX :: 86 + _SC_THREAD_PRIO_INHERIT :: 87 + _SC_THREAD_PRIO_PROTECT :: 88 + _SC_THREAD_PRIORITY_SCHEDULING :: 89 + + _SC_THREAD_PROCESS_SHARED :: 90 + _SC_THREAD_SAFE_FUNCTIONS :: 91 + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_THREAD_STACK_MIN :: 93 + _SC_THREAD_THREADS_MAX :: 94 + _SC_TIMEOUTS :: 95 + _SC_THREADS :: 96 + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + + _SC_TRACE_LOG :: 100 + _SC_TTY_NAME_MAX :: 101 + _SC_TYPED_MEMORY_OBJECTS :: 102 + _SC_V6_ILP32_OFF32 :: 103 + _SC_V6_ILP32_OFFBIG :: 104 + _SC_V6_LP64_OFF64 :: 105 + _SC_V6_LPBIG_OFFBIG :: 106 + _SC_ATEXIT_MAX :: 107 + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_SHM :: 113 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_SYMLOOP_MAX :: 120 + _SC_SS_REPL_MAX :: 126 // NOTE: not in headers + _SC_TRACE_EVENT_NAME_MAX :: 127 // NOTE: not in headers + _SC_TRACE_NAME_MAX :: 128 // NOTE: not in headers + _SC_TRACE_SYS_MAX :: 129 // NOTE: not in headers + _SC_TRACE_USER_EVENT_MAX :: 130 // NOTE: not in headers + + _POSIX_VDISABLE :: 0xff + +} else when ODIN_OS == .NetBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 13 // NOTE: not in headers + _PC_ALLOC_SIZE_MIN :: 10 // NOTE: not in headers + _PC_ASYNC_IO :: 53 // NOTE: not in headers + _PC_FILESIZEBITS :: 11 + _PC_PRIO_IO :: 54 // NOTE: not in headers + _PC_REC_INCR_XFER_SIZE :: 14 // NOTE: not in headers + _PC_REC_MAX_XFER_SIZE :: 15 // NOTE: not in headers + _PC_REC_MIN_XFER_SIZE :: 16 // NOTE: not in headers + _PC_REC_XFER_ALIGN :: 17 // NOTE: not in headers + _PC_SYMLINK_MAX :: 12 + _PC_SYNC_IO :: 10 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_PAGE_SIZE :: 28 + _SC_PAGESIZE :: _SC_PAGE_SIZE + _SC_FSYNC :: 29 + + _SC_XOPEN_SHM :: 30 + _SC_SYNCHRONIZED_IO :: 31 + _SC_IOV_MAX :: 32 + _SC_MAPPED_FILES :: 33 + _SC_MEMLOCK :: 34 + _SC_MEMLOCK_RANGE :: 35 + _SC_MEMORY_PROTECTION :: 36 + _SC_LOGIN_NAME_MAX :: 37 + _SC_MONOTONIC_CLOCK :: 38 + _SC_CLK_TCK :: 39 + + _SC_ATEXIT_MAX :: 40 + _SC_THREADS :: 41 + _SC_SEMAPHORES :: 42 + _SC_BARRIERS :: 43 + _SC_TIMERS :: 44 + _SC_SPIN_LOCKS :: 45 + _SC_READER_WRITER_LOCKS :: 46 + _SC_GETGR_R_SIZE_MAX :: 47 + _SC_GETPW_R_SIZE_MAX :: 48 + _SC_CLOCK_SELECTION :: 49 + + _SC_ASYNCHRONOUS_IO :: 50 + _SC_AIO_LISTIO_MAX :: 51 + _SC_AIO_MAX :: 52 + _SC_MESSAGE_PASSING :: 53 + _SC_MQ_OPEN_MAX :: 54 + _SC_MQ_PRIO_MAX :: 55 + _SC_PRIORITY_SCHEDULING :: 56 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 57 + _SC_THREAD_KEYS_MAX :: 58 + _SC_THREAD_STACK_MIN :: 59 + + _SC_THREAD_THREADS_MAX :: 60 + _SC_THREAD_ATTR_STACKADDR :: 61 + _SC_THREAD_ATTR_STACKSIZE :: 62 + _SC_THREAD_PRIORITY_SCHEDULING :: 63 + _SC_THREAD_PRIO_INHERIT :: 64 + _SC_THREAD_PRIO_PROTECT :: 65 + _SC_THREAD_PROCESS_SHARED :: 66 + _SC_THREAD_SAFE_FUNCTIONS :: 67 + _SC_TTY_NAME_MAX :: 68 + _SC_HOST_NAME_MAX :: 69 + + _SC_PASS_MAX :: 70 + _SC_REGEXP :: 71 + _SC_SHELL :: 72 + _SC_SYMLOOP_MAX :: 73 + _SC_V6_ILP32_OFF32 :: 74 + _SC_V6_ILP32_OFFBIG :: 75 + _SC_V6_LP64_OFF64 :: 76 + _SC_V6_LPBIG_OFFBIG :: 77 + + _SC_2_PBS :: 80 + _SC_2_PBS_ACCOUNTING :: 81 + _SC_2_PBS_CHECKPOINT :: 82 + _SC_2_PBS_LOCATE :: 83 + _SC_2_PBS_MESSAGE :: 84 + _SC_2_PBS_TRACK :: 85 + _SC_SPAWN :: 86 + _SC_SHARED_MEMORY_OBJECTS :: 87 + _SC_TIMER_MAX :: 88 + _SC_SEM_NSEMS_MAX :: 89 + + _SC_CPUTIME :: 90 + _SC_THREAD_CPUTIME :: 91 + _SC_DELAYTIMER_MAX :: 92 + _SC_SIGQUEUE_MAX :: 93 + _SC_REALTIME_SIGNALS :: 94 + _SC_RTSIG_MAX :: 95 + + _POSIX_VDISABLE :: '\377' + + // NOTE: following are not defined in netbsd headers. + + _SC_SPORADIC_SERVER :: 81 + _SC_SEM_VALUE_MAX :: 50 + + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + _SC_TRACE_LOG :: 100 + _SC_TYPED_MEMORY_OBJECTS :: 102 + + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_TIMEOUTS :: 95 + + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_PRIORITIZED_IO :: 34 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_ADVISORY_INFO :: 65 + _SC_SS_REPL_MAX :: 126 + _SC_TRACE_EVENT_NAME_MAX :: 127 + _SC_TRACE_NAME_MAX :: 128 + _SC_TRACE_SYS_MAX :: 129 + _SC_TRACE_USER_EVENT_MAX :: 130 + +} else when ODIN_OS == .OpenBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 10 + _PC_ALLOC_SIZE_MIN :: 11 + _PC_ASYNC_IO :: 12 + _PC_FILESIZEBITS :: 13 + _PC_PRIO_IO :: 14 + _PC_REC_INCR_XFER_SIZE :: 15 + _PC_REC_MAX_XFER_SIZE :: 16 + _PC_REC_MIN_XFER_SIZE :: 17 + _PC_REC_XFER_ALIGN :: 18 + _PC_SYMLINK_MAX :: 19 + _PC_SYNC_IO :: 20 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_PAGESIZE :: 28 + _SC_PAGE_SIZE :: _SC_PAGESIZE + _SC_FSYNC :: 29 + + _SC_XOPEN_SHM :: 30 + _SC_SEM_NSEMS_MAX :: 31 + _SC_SEM_VALUE_MAX :: 32 + _SC_HOST_NAME_MAX :: 33 + _SC_MONOTONIC_CLOCK :: 34 + _SC_2_PBS :: 35 + _SC_2_PBS_ACCOUNTING :: 36 + _SC_2_PBS_CHECKPOINT :: 37 + _SC_2_PBS_LOCATE :: 38 + _SC_2_PBS_MESSAGE :: 39 + + _SC_2_PBS_TRACK :: 40 + _SC_ADVISORY_INFO :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_ASYNCHRONOUS_IO :: 45 + _SC_ATEXIT_MAX :: 46 + _SC_BARRIERS :: 47 + _SC_CLOCK_SELECTION :: 48 + _SC_CPUTIME :: 49 + + _SC_DELAYTIMER_MAX :: 50 + _SC_IOV_MAX :: 51 + _SC_IPV6 :: 52 + _SC_MAPPED_FILES :: 53 + _SC_MEMLOCK :: 54 + _SC_MEMLOCK_RANGE :: 55 + _SC_MEMORY_PROTECTION :: 56 + _SC_MESSAGE_PASSING :: 57 + _SC_MQ_OPEN_MAX :: 58 + _SC_MQ_PRIO_MAX :: 59 + + _SC_PRIORITIZED_IO :: 60 + _SC_PRIORITY_SCHEDULING :: 61 + _SC_RAW_SOCKETS :: 62 + _SC_READER_WRITER_LOCKS :: 63 + _SC_REALTIME_SIGNALS :: 64 + _SC_REGEXP :: 65 + _SC_RTSIG_MAX :: 66 + _SC_SEMAPHORES :: 67 + _SC_SHARED_MEMORY_OBJECTS :: 68 + _SC_SHELL :: 69 + + _SC_SIGQUEUE_MAX :: 70 + _SC_SPAWN :: 71 + _SC_SPIN_LOCKS :: 72 + _SC_SPORADIC_SERVER :: 73 + _SC_SS_REPL_MAX :: 74 + _SC_SYNCHRONIZED_IO :: 75 + _SC_SYMLOOP_MAX :: 76 + _SC_THREAD_ATTR_STACKADDR :: 77 + _SC_THREAD_ATTR_STACKSIZE :: 78 + _SC_THREAD_CPUTIME :: 79 + + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 80 + _SC_THREAD_KEYS_MAX :: 81 + _SC_THREAD_PRIO_INHERIT :: 82 + _SC_THREAD_PRIO_PROTECT :: 83 + _SC_THREAD_PRIORITY_SCHEDULING :: 84 + _SC_THREAD_PROCESS_SHARED :: 85 + _SC_THREAD_ROBUST_PRIO_INHERIT :: 86 + _SC_THREAD_ROBUST_PRIO_PROTECT :: 87 + _SC_THREAD_SPORADIC_SERVER :: 88 + _SC_THREAD_STACK_MIN :: 89 + + _SC_THREAD_THREADS_MAX :: 90 + _SC_THREADS :: 91 + _SC_TIMEOUTS :: 92 + _SC_TIMER_MAX :: 93 + _SC_TIMERS :: 94 + _SC_TRACE :: 95 + _SC_TRACE_EVENT_FILTER :: 96 + _SC_TRACE_EVENT_NAME_MAX :: 97 + _SC_TRACE_INHERIT :: 98 + _SC_TRACE_LOG :: 99 + + _SC_GETGR_R_SIZE_MAX :: 100 + _SC_GETPW_R_SIZE_MAX :: 101 + _SC_LOGIN_NAME_MAX :: 102 + _SC_THREAD_SAFE_FUNCTIONS :: 103 + _SC_TRACE_NAME_MAX :: 104 + _SC_TRACE_SYS_MAX :: 105 + _SC_TRACE_USER_EVENT_MAX :: 106 + _SC_TTY_NAME_MAX :: 107 + _SC_TYPED_MEMORY_OBJECTS :: 108 + _SC_V6_ILP32_OFF32 :: 109 + + _SC_V6_ILP32_OFFBIG :: 110 + _SC_V6_LP64_OFF64 :: 111 + _SC_V6_LPBIG_OFFBIG :: 112 + _SC_V7_ILP32_OFF32 :: 113 + _SC_V7_ILP32_OFFBIG :: 114 + _SC_V7_LP64_OFF64 :: 115 + _SC_V7_LPBIG_OFFBIG :: 116 + _SC_XOPEN_CRYPT :: 117 + _SC_XOPEN_ENH_I18N :: 118 + _SC_XOPEN_LEGACY :: 119 + + _SC_XOPEN_REALTIME :: 120 + _SC_XOPEN_REALTIME_THREADS :: 121 + _SC_XOPEN_STREAMS :: 122 + _SC_XOPEN_UNIX :: 123 + _SC_XOPEN_UUCP :: 124 + _SC_XOPEN_VERSION :: 125 + + _SC_PHYS_PAGES :: 500 + _SC_AVPHYS_PAGES :: 501 + _SC_NPROCESSORS_CONF :: 502 + _SC_NPROCESSORS_ONLN :: 503 + + _POSIX_VDISABLE :: '\377' + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/utime.odin b/core/sys/posix/utime.odin new file mode 100644 index 000000000..591a6db06 --- /dev/null +++ b/core/sys/posix/utime.odin @@ -0,0 +1,36 @@ +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// utime.h - access and modification time structure + +foreign lib { + /* + Set file access and modification times. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/utime.html ]] + */ + @(link_name=LUTIME) + utime :: proc(path: cstring, times: ^utimbuf) -> result --- +} + +when ODIN_OS == .NetBSD { + @(private) LUTIME :: "__utime50" +} else { + @(private) LUTIME :: "utime" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + utimbuf :: struct { + actime: time_t, /* [PSX] access time (seconds since epoch) */ + modtime: time_t, /* [PSX] modification time (seconds since epoch) */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/wordexp.odin b/core/sys/posix/wordexp.odin new file mode 100644 index 000000000..d730db0f7 --- /dev/null +++ b/core/sys/posix/wordexp.odin @@ -0,0 +1,107 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// wordexp.h - word-expansion type + +foreign lib { + /* + Perform word expansion. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wordexp.html ]] + */ + wordexp :: proc(words: cstring, pwordexp: ^wordexp_t, flags: WRDE_Flags) -> WRDE_Errno --- + + /* + Free the space allocated during word expansion. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wordexp.html ]] + */ + wordfree :: proc(pwordexp: ^wordexp_t) --- +} + +WRDE_Flag_Bits :: enum c.int { + // Appends words to those previously generated. + APPEND = log2(WRDE_APPEND), + // Number of null pointers to prepend to we_wordv. + DOOFFS = log2(WRDE_DOOFFS), + // Fail if command substitution is requested. + NOCMD = log2(WRDE_NOCMD), + // The pwordexp argument was passed to a previous successful call to wordexp(), + // and has not been passed to wordfree(). + REUSE = log2(WRDE_REUSE), + // Do not redirect stderr to /dev/null. + SHOWERR = log2(WRDE_SHOWERR), + // Report error on attempt to expand an undefined shell variable. + UNDEF = log2(WRDE_UNDEF), +} +WRDE_Flags :: bit_set[WRDE_Flag_Bits; c.int] + +WRDE_Errno :: enum c.int { + OK = 0, + // One of the unquoted characters- , '|', '&', ';', '<', '>', '(', ')', '{', '}' - + // appears in words in an inappropriate context. + BADCHAR = WRDE_BADCHAR, + // Reference to undefined shell variable when WRDE_UNDEF is set in flags. + BADVAL = WRDE_BADVAL, + // Command substitution requested when WRDE_NOCMD was set in flags. + CMDSUB = WRDE_CMDSUB, + // Attempt to allocate memory failed. + NOSPACE = WRDE_NOSPACE, + // Shell syntax error, such as unbalanced parentheses or an unterminated string. + SYNTAX = WRDE_SYNTAX, +} + +when ODIN_OS == .Darwin { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + } + + WRDE_APPEND :: 0x01 + WRDE_DOOFFS :: 0x02 + WRDE_NOCMD :: 0x04 + WRDE_REUSE :: 0x08 + WRDE_SHOWERR :: 0x10 + WRDE_UNDEF :: 0x20 + + WRDE_BADCHAR :: 1 + WRDE_BADVAL :: 2 + WRDE_CMDSUB :: 3 + WRDE_NOSPACE :: 4 + WRDE_SYNTAX :: 6 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + we_strings: [^]byte, /* storage for wordv strings */ + we_nbytes: c.size_t, /* size of we_strings */ + } + + WRDE_APPEND :: 0x01 + WRDE_DOOFFS :: 0x02 + WRDE_NOCMD :: 0x04 + WRDE_REUSE :: 0x08 + WRDE_SHOWERR :: 0x10 + WRDE_UNDEF :: 0x20 + + WRDE_BADCHAR :: 1 + WRDE_BADVAL :: 2 + WRDE_CMDSUB :: 3 + WRDE_NOSPACE :: 4 + WRDE_SYNTAX :: 6 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/unix/pthread_netbsd.odin b/core/sys/unix/pthread_netbsd.odin index afbbc321c..9107f1139 100644 --- a/core/sys/unix/pthread_netbsd.odin +++ b/core/sys/unix/pthread_netbsd.odin @@ -2,7 +2,7 @@ package unix import "core:c" -pthread_t :: distinct u64 +pthread_t :: distinct rawptr SEM_T_SIZE :: 8 diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index c876a214a..26a21e629 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -5,6 +5,11 @@ foreign import "system:pthread" import "core:c" +timespec :: struct { + tv_sec: i64, + tv_nsec: i64, +} + // // On success, these functions return 0. // @@ -22,6 +27,8 @@ foreign pthread { pthread_equal :: proc(a, b: pthread_t) -> b32 --- + pthread_detach :: proc(t: pthread_t) -> c.int --- + sched_get_priority_min :: proc(policy: c.int) -> c.int --- sched_get_priority_max :: proc(policy: c.int) -> c.int --- diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 038c16276..89ad2661f 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1514,6 +1514,338 @@ when ODIN_ARCH == .amd64 { SYS_landlock_create_ruleset : uintptr : 444 SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 +} else when ODIN_ARCH == .riscv64 { + SYS_io_setup :: uintptr(0) + SYS_io_destroy :: uintptr(1) + SYS_io_submit :: uintptr(2) + SYS_io_cancel :: uintptr(3) + SYS_io_getevents :: uintptr(4) + SYS_setxattr :: uintptr(5) + SYS_lsetxattr :: uintptr(6) + SYS_fsetxattr :: uintptr(7) + SYS_getxattr :: uintptr(8) + SYS_lgetxattr :: uintptr(9) + SYS_fgetxattr :: uintptr(10) + SYS_listxattr :: uintptr(11) + SYS_llistxattr :: uintptr(12) + SYS_flistxattr :: uintptr(13) + SYS_removexattr :: uintptr(14) + SYS_lremovexattr :: uintptr(15) + SYS_fremovexattr :: uintptr(16) + SYS_getcwd :: uintptr(17) + SYS_lookup_dcookie :: uintptr(18) + SYS_eventfd2 :: uintptr(19) + SYS_epoll_create1 :: uintptr(20) + SYS_epoll_ctl :: uintptr(21) + SYS_epoll_pwait :: uintptr(22) + SYS_dup :: uintptr(23) + SYS_dup3 :: uintptr(24) + SYS_fcntl :: uintptr(25) + SYS_inotify_init1 :: uintptr(26) + SYS_inotify_add_watch :: uintptr(27) + SYS_inotify_rm_watch :: uintptr(28) + SYS_ioctl :: uintptr(29) + SYS_ioprio_set :: uintptr(30) + SYS_ioprio_get :: uintptr(31) + SYS_flock :: uintptr(32) + SYS_mknodat :: uintptr(33) + SYS_mkdirat :: uintptr(34) + SYS_unlinkat :: uintptr(35) + SYS_symlinkat :: uintptr(36) + SYS_linkat :: uintptr(37) + SYS_renameat :: uintptr(38) + SYS_umount2 :: uintptr(39) + SYS_mount :: uintptr(40) + SYS_pivot_root :: uintptr(41) + SYS_nfsservctl :: uintptr(42) + SYS_statfs :: uintptr(43) + SYS_fstatfs :: uintptr(44) + SYS_truncate :: uintptr(45) + SYS_ftruncate :: uintptr(46) + SYS_fallocate :: uintptr(47) + SYS_faccessat :: uintptr(48) + SYS_chdir :: uintptr(49) + SYS_fchdir :: uintptr(50) + SYS_chroot :: uintptr(51) + SYS_fchmod :: uintptr(52) + SYS_fchmodat :: uintptr(53) + SYS_fchownat :: uintptr(54) + SYS_fchown :: uintptr(55) + SYS_openat :: uintptr(56) + SYS_close :: uintptr(57) + SYS_vhangup :: uintptr(58) + SYS_pipe2 :: uintptr(59) + SYS_quotactl :: uintptr(60) + SYS_getdents64 :: uintptr(61) + SYS_lseek :: uintptr(62) + SYS_read :: uintptr(63) + SYS_write :: uintptr(64) + SYS_readv :: uintptr(65) + SYS_writev :: uintptr(66) + SYS_pread64 :: uintptr(67) + SYS_pwrite64 :: uintptr(68) + SYS_preadv :: uintptr(69) + SYS_pwritev :: uintptr(70) + SYS_sendfile :: uintptr(71) + SYS_pselect6 :: uintptr(72) + SYS_ppoll :: uintptr(73) + SYS_signalfd4 :: uintptr(74) + SYS_vmsplice :: uintptr(75) + SYS_splice :: uintptr(76) + SYS_tee :: uintptr(77) + SYS_readlinkat :: uintptr(78) + SYS_fstatat :: uintptr(79) + SYS_fstat :: uintptr(80) + SYS_sync :: uintptr(81) + SYS_fsync :: uintptr(82) + SYS_fdatasync :: uintptr(83) + SYS_sync_file_range2 :: uintptr(84) + SYS_sync_file_range :: uintptr(84) + SYS_timerfd_create :: uintptr(85) + SYS_timerfd_settime :: uintptr(86) + SYS_timerfd_gettime :: uintptr(87) + SYS_utimensat :: uintptr(88) + SYS_acct :: uintptr(89) + SYS_capget :: uintptr(90) + SYS_capset :: uintptr(91) + SYS_personality :: uintptr(92) + SYS_exit :: uintptr(93) + SYS_exit_group :: uintptr(94) + SYS_waitid :: uintptr(95) + SYS_set_tid_address :: uintptr(96) + SYS_unshare :: uintptr(97) + SYS_futex :: uintptr(98) + SYS_set_robust_list :: uintptr(99) + SYS_get_robust_list :: uintptr(100) + SYS_nanosleep :: uintptr(101) + SYS_getitimer :: uintptr(102) + SYS_setitimer :: uintptr(103) + SYS_kexec_load :: uintptr(104) + SYS_init_module :: uintptr(105) + SYS_delete_module :: uintptr(106) + SYS_timer_create :: uintptr(107) + SYS_timer_gettime :: uintptr(108) + SYS_timer_getoverrun :: uintptr(109) + SYS_timer_settime :: uintptr(110) + SYS_timer_delete :: uintptr(111) + SYS_clock_settime :: uintptr(112) + SYS_clock_gettime :: uintptr(113) + SYS_clock_getres :: uintptr(114) + SYS_clock_nanosleep :: uintptr(115) + SYS_syslog :: uintptr(116) + SYS_ptrace :: uintptr(117) + SYS_sched_setparam :: uintptr(118) + SYS_sched_setscheduler :: uintptr(119) + SYS_sched_getscheduler :: uintptr(120) + SYS_sched_getparam :: uintptr(121) + SYS_sched_setaffinity :: uintptr(122) + SYS_sched_getaffinity :: uintptr(123) + SYS_sched_yield :: uintptr(124) + SYS_sched_get_priority_max :: uintptr(125) + SYS_sched_get_priority_min :: uintptr(126) + SYS_sched_rr_get_interval :: uintptr(127) + SYS_restart_syscall :: uintptr(128) + SYS_kill :: uintptr(129) + SYS_tkill :: uintptr(130) + SYS_tgkill :: uintptr(131) + SYS_sigaltstack :: uintptr(132) + SYS_rt_sigsuspend :: uintptr(133) + SYS_rt_sigaction :: uintptr(134) + SYS_rt_sigprocmask :: uintptr(135) + SYS_rt_sigpending :: uintptr(136) + SYS_rt_sigtimedwait :: uintptr(137) + SYS_rt_sigqueueinfo :: uintptr(138) + SYS_rt_sigreturn :: uintptr(139) + SYS_setpriority :: uintptr(140) + SYS_getpriority :: uintptr(141) + SYS_reboot :: uintptr(142) + SYS_setregid :: uintptr(143) + SYS_setgid :: uintptr(144) + SYS_setreuid :: uintptr(145) + SYS_setuid :: uintptr(146) + SYS_setresuid :: uintptr(147) + SYS_getresuid :: uintptr(148) + SYS_setresgid :: uintptr(149) + SYS_getresgid :: uintptr(150) + SYS_setfsuid :: uintptr(151) + SYS_setfsgid :: uintptr(152) + SYS_times :: uintptr(153) + SYS_setpgid :: uintptr(154) + SYS_getpgid :: uintptr(155) + SYS_getsid :: uintptr(156) + SYS_setsid :: uintptr(157) + SYS_getgroups :: uintptr(158) + SYS_setgroups :: uintptr(159) + SYS_uname :: uintptr(160) + SYS_sethostname :: uintptr(161) + SYS_setdomainname :: uintptr(162) + SYS_getrlimit :: uintptr(163) + SYS_setrlimit :: uintptr(164) + SYS_getrusage :: uintptr(165) + SYS_umask :: uintptr(166) + SYS_prctl :: uintptr(167) + SYS_getcpu :: uintptr(168) + SYS_gettimeofday :: uintptr(169) + SYS_settimeofday :: uintptr(170) + SYS_adjtimex :: uintptr(171) + SYS_getpid :: uintptr(172) + SYS_getppid :: uintptr(173) + SYS_getuid :: uintptr(174) + SYS_geteuid :: uintptr(175) + SYS_getgid :: uintptr(176) + SYS_getegid :: uintptr(177) + SYS_gettid :: uintptr(178) + SYS_sysinfo :: uintptr(179) + SYS_mq_open :: uintptr(180) + SYS_mq_unlink :: uintptr(181) + SYS_mq_timedsend :: uintptr(182) + SYS_mq_timedreceive :: uintptr(183) + SYS_mq_notify :: uintptr(184) + SYS_mq_getsetattr :: uintptr(185) + SYS_msgget :: uintptr(186) + SYS_msgctl :: uintptr(187) + SYS_msgrcv :: uintptr(188) + SYS_msgsnd :: uintptr(189) + SYS_semget :: uintptr(190) + SYS_semctl :: uintptr(191) + SYS_semtimedop :: uintptr(192) + SYS_semop :: uintptr(193) + SYS_shmget :: uintptr(194) + SYS_shmctl :: uintptr(195) + SYS_shmat :: uintptr(196) + SYS_shmdt :: uintptr(197) + SYS_socket :: uintptr(198) + SYS_socketpair :: uintptr(199) + SYS_bind :: uintptr(200) + SYS_listen :: uintptr(201) + SYS_accept :: uintptr(202) + SYS_connect :: uintptr(203) + SYS_getsockname :: uintptr(204) + SYS_getpeername :: uintptr(205) + SYS_sendto :: uintptr(206) + SYS_recvfrom :: uintptr(207) + SYS_setsockopt :: uintptr(208) + SYS_getsockopt :: uintptr(209) + SYS_shutdown :: uintptr(210) + SYS_sendmsg :: uintptr(211) + SYS_recvmsg :: uintptr(212) + SYS_readahead :: uintptr(213) + SYS_brk :: uintptr(214) + SYS_munmap :: uintptr(215) + SYS_mremap :: uintptr(216) + SYS_add_key :: uintptr(217) + SYS_request_key :: uintptr(218) + SYS_keyctl :: uintptr(219) + SYS_clone :: uintptr(220) + SYS_execve :: uintptr(221) + SYS_mmap :: uintptr(222) + SYS_fadvise64 :: uintptr(223) + SYS_swapon :: uintptr(224) + SYS_swapoff :: uintptr(225) + SYS_mprotect :: uintptr(226) + SYS_msync :: uintptr(227) + SYS_mlock :: uintptr(228) + SYS_munlock :: uintptr(229) + SYS_mlockall :: uintptr(230) + SYS_munlockall :: uintptr(231) + SYS_mincore :: uintptr(232) + SYS_madvise :: uintptr(233) + SYS_remap_file_pages :: uintptr(234) + SYS_mbind :: uintptr(235) + SYS_get_mempolicy :: uintptr(236) + SYS_set_mempolicy :: uintptr(237) + SYS_migrate_pages :: uintptr(238) + SYS_move_pages :: uintptr(239) + SYS_rt_tgsigqueueinfo :: uintptr(240) + SYS_perf_event_open :: uintptr(241) + SYS_accept4 :: uintptr(242) + SYS_recvmmsg :: uintptr(243) + SYS_wait4 :: uintptr(260) + SYS_prlimit64 :: uintptr(261) + SYS_fanotify_init :: uintptr(262) + SYS_fanotify_mark :: uintptr(263) + SYS_name_to_handle_at :: uintptr(264) + SYS_open_by_handle_at :: uintptr(265) + SYS_clock_adjtime :: uintptr(266) + SYS_syncfs :: uintptr(267) + SYS_setns :: uintptr(268) + SYS_sendmmsg :: uintptr(269) + SYS_process_vm_readv :: uintptr(270) + SYS_process_vm_writev :: uintptr(271) + SYS_kcmp :: uintptr(272) + SYS_finit_module :: uintptr(273) + SYS_sched_setattr :: uintptr(274) + SYS_sched_getattr :: uintptr(275) + SYS_renameat2 :: uintptr(276) + SYS_seccomp :: uintptr(277) + SYS_getrandom :: uintptr(278) + SYS_memfd_create :: uintptr(279) + SYS_bpf :: uintptr(280) + SYS_execveat :: uintptr(281) + SYS_userfaultfd :: uintptr(282) + SYS_membarrier :: uintptr(283) + SYS_mlock2 :: uintptr(284) + SYS_copy_file_range :: uintptr(285) + SYS_preadv2 :: uintptr(286) + SYS_pwritev2 :: uintptr(287) + SYS_pkey_mprotect :: uintptr(288) + SYS_pkey_alloc :: uintptr(289) + SYS_pkey_free :: uintptr(290) + SYS_statx :: uintptr(291) + SYS_io_pgetevents :: uintptr(292) + SYS_rseq :: uintptr(293) + SYS_kexec_file_load :: uintptr(294) + SYS_clock_gettime64 :: uintptr(403) + SYS_clock_settime64 :: uintptr(404) + SYS_clock_adjtime64 :: uintptr(405) + SYS_clock_getres_time64 :: uintptr(406) + SYS_clock_nanosleep_time64 :: uintptr(407) + SYS_timer_gettime64 :: uintptr(408) + SYS_timer_settime64 :: uintptr(409) + SYS_timerfd_gettime64 :: uintptr(410) + SYS_timerfd_settime64 :: uintptr(411) + SYS_utimensat_time64 :: uintptr(412) + SYS_pselect6_time64 :: uintptr(413) + SYS_ppoll_time64 :: uintptr(414) + SYS_io_pgetevents_time64 :: uintptr(416) + SYS_recvmmsg_time64 :: uintptr(417) + SYS_mq_timedsend_time64 :: uintptr(418) + SYS_mq_timedreceive_time64 :: uintptr(419) + SYS_semtimedop_time64 :: uintptr(420) + SYS_rt_sigtimedwait_time64 :: uintptr(421) + SYS_futex_time64 :: uintptr(422) + SYS_sched_rr_get_interval_time64 :: uintptr(423) + SYS_pidfd_send_signal :: uintptr(424) + SYS_io_uring_setup :: uintptr(425) + SYS_io_uring_enter :: uintptr(426) + SYS_io_uring_register :: uintptr(427) + SYS_open_tree :: uintptr(428) + SYS_move_mount :: uintptr(429) + SYS_fsopen :: uintptr(430) + SYS_fsconfig :: uintptr(431) + SYS_fsmount :: uintptr(432) + SYS_fspick :: uintptr(433) + SYS_pidfd_open :: uintptr(434) + SYS_clone3 :: uintptr(435) + SYS_close_range :: uintptr(436) + SYS_openat2 :: uintptr(437) + SYS_pidfd_getfd :: uintptr(438) + SYS_faccessat2 :: uintptr(439) + SYS_process_madvise :: uintptr(440) + SYS_epoll_pwait2 :: uintptr(441) + SYS_mount_setattr :: uintptr(442) + SYS_quotactl_fd :: uintptr(443) + SYS_landlock_create_ruleset :: uintptr(444) + SYS_landlock_add_rule :: uintptr(445) + SYS_landlock_restrict_self :: uintptr(446) + SYS_memfd_secret :: uintptr(447) + SYS_process_mrelease :: uintptr(448) + SYS_futex_waitv :: uintptr(449) + SYS_set_mempolicy_home_node :: uintptr(450) + SYS_cachestat :: uintptr(451) + SYS_fchmodat2 :: uintptr(452) + + SIGCHLD :: 17 } else { #panic("Unsupported architecture") } @@ -1742,7 +2074,7 @@ sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: uint, flags: int) -> } sys_open :: proc "contextless" (path: cstring, flags: int, mode: uint = 0o000) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open return int(intrinsics.syscall(SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) @@ -1762,7 +2094,7 @@ sys_read :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { } sys_pread :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { low := uintptr(offset & 0xFFFFFFFF) @@ -1776,7 +2108,7 @@ sys_write :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { } sys_pwrite :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { low := uintptr(offset & 0xFFFFFFFF) @@ -1786,7 +2118,7 @@ sys_pwrite :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) } sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) } else { low := uintptr(offset & 0xFFFFFFFF) @@ -1800,7 +2132,7 @@ sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 { sys_stat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != .arm64 { + } else when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have stat return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0)) @@ -1808,7 +2140,7 @@ sys_stat :: proc "contextless" (path: cstring, stat: rawptr) -> int { } sys_fstat :: proc "contextless" (fd: int, stat: rawptr) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) } else { return int(intrinsics.syscall(SYS_fstat64, uintptr(fd), uintptr(stat))) @@ -1818,7 +2150,7 @@ sys_fstat :: proc "contextless" (fd: int, stat: rawptr) -> int { sys_lstat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != .arm64 { + } else when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have any lstat return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) @@ -1826,7 +2158,7 @@ sys_lstat :: proc "contextless" (path: cstring, stat: rawptr) -> int { } sys_readlink :: proc "contextless" (path: cstring, buf: rawptr, bufsiz: uint) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink return int(intrinsics.syscall(SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) @@ -1834,7 +2166,7 @@ sys_readlink :: proc "contextless" (path: cstring, buf: rawptr, bufsiz: uint) -> } sys_symlink :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have symlink return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)))) @@ -1842,7 +2174,7 @@ sys_symlink :: proc "contextless" (old_name: cstring, new_name: cstring) -> int } sys_access :: proc "contextless" (path: cstring, mask: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access return int(intrinsics.syscall(SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask))) @@ -1862,7 +2194,7 @@ sys_fchdir :: proc "contextless" (fd: int) -> int { } sys_chmod :: proc "contextless" (path: cstring, mode: uint) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have chmod return int(intrinsics.syscall(SYS_fchmodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) @@ -1874,7 +2206,7 @@ sys_fchmod :: proc "contextless" (fd: int, mode: uint) -> int { } sys_chown :: proc "contextless" (path: cstring, user: int, group: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH !=. riscv64 { return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have chown return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) @@ -1886,7 +2218,7 @@ sys_fchown :: proc "contextless" (fd: int, user: int, group: int) -> int { } sys_lchown :: proc "contextless" (path: cstring, user: int, group: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have lchown return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) @@ -1894,7 +2226,7 @@ sys_lchown :: proc "contextless" (path: cstring, user: int, group: int) -> int { } sys_rename :: proc "contextless" (old, new: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename return int(intrinsics.syscall(SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new)))) @@ -1902,7 +2234,7 @@ sys_rename :: proc "contextless" (old, new: cstring) -> int { } sys_link :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have link return int(intrinsics.syscall(SYS_linkat, AT_FDCWD, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) @@ -1910,7 +2242,7 @@ sys_link :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { } sys_unlink :: proc "contextless" (path: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0)) @@ -1922,7 +2254,7 @@ sys_unlinkat :: proc "contextless" (dfd: int, path: cstring, flag: int = 0) -> i } sys_rmdir :: proc "contextless" (path: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR)) @@ -1930,7 +2262,7 @@ sys_rmdir :: proc "contextless" (path: cstring) -> int { } sys_mkdir :: proc "contextless" (path: cstring, mode: uint) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir return int(intrinsics.syscall(SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) @@ -1942,7 +2274,7 @@ sys_mkdirat :: proc "contextless" (dfd: int, path: cstring, mode: uint) -> int { } sys_mknod :: proc "contextless" (path: cstring, mode: uint, dev: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } else { // NOTE: arm64 does not have mknod return int(intrinsics.syscall(SYS_mknodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) @@ -1954,7 +2286,7 @@ sys_mknodat :: proc "contextless" (dfd: int, path: cstring, mode: uint, dev: int } sys_truncate :: proc "contextless" (path: cstring, length: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) } else { low := uintptr(length & 0xFFFFFFFF) @@ -1964,7 +2296,7 @@ sys_truncate :: proc "contextless" (path: cstring, length: i64) -> int { } sys_ftruncate :: proc "contextless" (fd: int, length: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_ftruncate, uintptr(fd), uintptr(length))) } else { low := uintptr(length & 0xFFFFFFFF) @@ -1982,7 +2314,7 @@ sys_getdents64 :: proc "contextless" (fd: int, dirent: rawptr, count: int) -> in } sys_fork :: proc "contextless" () -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_fork)) } else { return int(intrinsics.syscall(SYS_clone, SIGCHLD)) @@ -1992,7 +2324,7 @@ sys_pipe2 :: proc "contextless" (fds: rawptr, flags: int) -> int { return int(intrinsics.syscall(SYS_pipe2, uintptr(fds), uintptr(flags))) } sys_dup2 :: proc "contextless" (oldfd: int, newfd: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_dup2, uintptr(oldfd), uintptr(newfd))) } else { return int(intrinsics.syscall(SYS_dup3, uintptr(oldfd), uintptr(newfd), 0)) @@ -2076,7 +2408,7 @@ sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int { sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { // NOTE: specialcased here because `arm64` does not have `poll` - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { seconds := i64(timeout / 1_000) nanoseconds := i64((timeout % 1000) * 1_000_000) timeout_spec := timespec{seconds, nanoseconds} diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 14d3c113a..92222bdfe 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -67,6 +67,8 @@ CTL_KERN :: 1 KERN_VERSION :: 4 // Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:darwin-8020.121.3~4/RELEASE_X86_64 KERN_OSRELDATE :: 26 // i32: OS release date KERN_OSVERSION :: 65 // Build number, e.g. 21F79 + KERN_PROCARGS :: 38 + KERN_PROCARGS2 :: 49 CTL_VM :: 2 CTL_VFS :: 3 CTL_NET :: 4 @@ -82,4 +84,4 @@ CTL_HW :: 6 HW_AVAILCPU :: 25 /* int: number of available CPUs */ CTL_MACHDEP :: 7 -CTL_USER :: 8 \ No newline at end of file +CTL_USER :: 8 diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index 35c5db02c..8ca40ef1b 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -23,6 +23,8 @@ CTL_KERN :: 1 KERN_OSRELEASE :: 2 KERN_OSREV :: 3 KERN_VERSION :: 4 + KERN_PROC :: 14 + KERN_PROC_PATHNAME :: 12 CTL_VM :: 2 CTL_VFS :: 3 CTL_NET :: 4 diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin deleted file mode 100644 index 442202d36..000000000 --- a/core/sys/unix/time_unix.odin +++ /dev/null @@ -1,83 +0,0 @@ -//+build linux, darwin, freebsd, openbsd, netbsd, haiku -package unix - -when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" -} else { - foreign import libc "system:c" -} - -import "core:c" - -when ODIN_OS == .NetBSD { - @(default_calling_convention="c") - foreign libc { - @(link_name="__clock_gettime50") clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- - @(link_name="__nanosleep50") nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- - @(link_name="sleep") sleep :: proc(seconds: c.uint) -> c.int --- - } -} else { - @(default_calling_convention="c") - foreign libc { - clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- - sleep :: proc(seconds: c.uint) -> c.int --- - nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- - } -} - -timespec :: struct { - tv_sec: i64, // seconds - tv_nsec: i64, // nanoseconds -} - -when ODIN_OS == .OpenBSD { - CLOCK_REALTIME :: 0 - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_MONOTONIC :: 3 - CLOCK_THREAD_CPUTIME_ID :: 4 - CLOCK_UPTIME :: 5 - CLOCK_BOOTTIME :: 6 - - // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC - CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC -} else { - CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. - CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_THREAD_CPUTIME_ID :: 3 - CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. - CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." - CLOCK_MONOTONIC_COARSE :: 6 - CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. - CLOCK_REALTIME_ALARM :: 8 - CLOCK_BOOTTIME_ALARM :: 9 -} - -// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. -// I do not know if Darwin programmers are used to the existance of these constants or not, so -// I'm leaving aliases to them for now. -CLOCK_SYSTEM :: CLOCK_REALTIME -CLOCK_CALENDAR :: CLOCK_MONOTONIC - -boot_time_in_nanoseconds :: proc "c" () -> i64 { - ts_now, ts_boottime: timespec - clock_gettime(CLOCK_REALTIME, &ts_now) - clock_gettime(CLOCK_BOOTTIME, &ts_boottime) - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec - return i64(ns) -} - -seconds_since_boot :: proc "c" () -> f64 { - ts_boottime: timespec - clock_gettime(CLOCK_BOOTTIME, &ts_boottime) - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 -} - -inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { - s, ns := nanoseconds / 1e9, nanoseconds % 1e9 - requested := timespec{tv_sec=s, tv_nsec=ns} - res = nanosleep(&requested, &remaining) - return -} - diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin index 6ae6c9151..38d95e754 100644 --- a/core/sys/wasm/wasi/wasi_api.odin +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -10,9 +10,9 @@ filesize_t :: distinct u64 timestamp_t :: distinct u64 clockid_t :: distinct u32 -CLOCK_MONOTONIC :: clockid_t(0) -CLOCK_PROCESS_CPUTIME_ID :: clockid_t(1) -CLOCK_REALTIME :: clockid_t(2) +CLOCK_REALTIME :: clockid_t(0) +CLOCK_MONOTONIC :: clockid_t(1) +CLOCK_PROCESS_CPUTIME_ID :: clockid_t(2) CLOCK_THREAD_CPUTIME_ID :: clockid_t(3) errno_t :: enum u16 { @@ -715,7 +715,7 @@ subscription_t :: struct { * The type of the event to which to subscribe, and its contents */ using contents: struct { - tag: u8, + tag: eventtype_t, using u: struct #raw_union { clock: subscription_clock_t, fd_read: subscription_fd_readwrite_t, diff --git a/core/sys/windows/dwmapi.odin b/core/sys/windows/dwmapi.odin index 91911baae..d0ffc6b46 100644 --- a/core/sys/windows/dwmapi.odin +++ b/core/sys/windows/dwmapi.odin @@ -4,7 +4,7 @@ package sys_windows foreign import dwmapi "system:Dwmapi.lib" DWMWINDOWATTRIBUTE :: enum { - DWMWA_NCRENDERING_ENABLED, + DWMWA_NCRENDERING_ENABLED = 1, DWMWA_NCRENDERING_POLICY, DWMWA_TRANSITIONS_FORCEDISABLED, DWMWA_ALLOW_NCPAINT, @@ -28,7 +28,7 @@ DWMWINDOWATTRIBUTE :: enum { DWMWA_TEXT_COLOR, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, DWMWA_SYSTEMBACKDROP_TYPE, - DWMWA_LAST, + DWMWA_LAST, } DWMNCRENDERINGPOLICY :: enum { diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 6788ed2ea..5cbafddba 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -28,7 +28,7 @@ foreign gdi32 { 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 --- + DescribePixelFormat :: proc(hdc: HDC, iPixelFormat: INT, nBytes: UINT, ppfd: ^PIXELFORMATDESCRIPTOR) -> INT --- SwapBuffers :: proc(hdc: HDC) -> BOOL --- SetDCBrushColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin old mode 100755 new mode 100644 index f2ca2e507..81db67185 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -85,6 +85,10 @@ foreign kernel32 { lpTargetFileName: LPCWSTR, lpSecurityAttributes: LPSECURITY_ATTRIBUTES) -> BOOL --- + CreateSymbolicLinkW :: proc(lpSymlinkFileName: LPCWSTR, + lpTargetFileName: LPCWSTR, + dwFlags: DWORD) -> BOOLEAN --- + GetFileInformationByHandleEx :: proc(hFile: HANDLE, fileInfoClass: FILE_INFO_BY_HANDLE_CLASS, lpFileInformation: LPVOID, @@ -396,6 +400,9 @@ foreign kernel32 { GlobalAlloc :: proc(flags: UINT, bytes: SIZE_T) -> LPVOID --- GlobalReAlloc :: proc(mem: LPVOID, bytes: SIZE_T, flags: UINT) -> LPVOID --- GlobalFree :: proc(mem: LPVOID) -> LPVOID --- + + GlobalLock :: proc(hMem: HGLOBAL) -> LPVOID --- + GlobalUnlock :: proc(hMem: HGLOBAL) -> BOOL --- ReadDirectoryChangesW :: proc( hDirectory: HANDLE, @@ -1171,17 +1178,17 @@ SYSTEM_POWER_STATUS :: struct { } AC_Line_Status :: enum BYTE { - Offline = 0, - Online = 1, - Unknown = 255, + Offline = 0, + Online = 1, + Unknown = 255, } Battery_Flag :: enum BYTE { - High = 0, - Low = 1, - Critical = 2, - Charging = 3, - No_Battery = 7, + High = 0, + Low = 1, + Critical = 2, + Charging = 3, + No_Battery = 7, } Battery_Flags :: bit_set[Battery_Flag; BYTE] diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index e10e53cf9..934d103e5 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2781,7 +2781,7 @@ CONTEXT :: struct { PCONTEXT :: ^CONTEXT LPCONTEXT :: ^CONTEXT -when size_of(uintptr) == 32 { +when size_of(uintptr) == 32 { XSAVE_FORMAT :: struct #align(16) { ControlWord: WORD, StatusWord: WORD, @@ -3546,11 +3546,11 @@ SIGDN :: enum c_int { } SIATTRIBFLAGS :: enum c_int { - AND = 0x1, - OR = 0x2, - APPCOMPAT = 0x3, - MASK = 0x3, - ALLITEMS = 0x4000, + AND = 0x1, + OR = 0x2, + APPCOMPAT = 0x3, + MASK = 0x3, + ALLITEMS = 0x4000, } FDAP :: enum c_int { @@ -4503,35 +4503,35 @@ DNS_INFO_NO_RECORDS :: 9501 DNS_QUERY_NO_RECURSION :: 0x00000004 DNS_RECORD :: struct { // aka DNS_RECORDA - pNext: ^DNS_RECORD, - pName: cstring, - wType: WORD, - wDataLength: USHORT, - Flags: DWORD, - dwTtl: DWORD, - _: DWORD, - Data: struct #raw_union { - CNAME: DNS_PTR_DATAA, - A: u32be, // Ipv4 Address - AAAA: u128be, // Ipv6 Address - TXT: DNS_TXT_DATAA, - NS: DNS_PTR_DATAA, - MX: DNS_MX_DATAA, - SRV: DNS_SRV_DATAA, - }, + pNext: ^DNS_RECORD, + pName: cstring, + wType: WORD, + wDataLength: USHORT, + Flags: DWORD, + dwTtl: DWORD, + _: DWORD, + Data: struct #raw_union { + CNAME: DNS_PTR_DATAA, + A: u32be, // Ipv4 Address + AAAA: u128be, // Ipv6 Address + TXT: DNS_TXT_DATAA, + NS: DNS_PTR_DATAA, + MX: DNS_MX_DATAA, + SRV: DNS_SRV_DATAA, + }, } DNS_TXT_DATAA :: struct { - dwStringCount: DWORD, - pStringArray: cstring, + dwStringCount: DWORD, + pStringArray: cstring, } DNS_PTR_DATAA :: cstring DNS_MX_DATAA :: struct { - pNameExchange: cstring, // the hostname - wPreference: WORD, // lower values preferred - _: WORD, // padding. + pNameExchange: cstring, // the hostname + wPreference: WORD, // lower values preferred + _: WORD, // padding. } DNS_SRV_DATAA :: struct { pNameTarget: cstring, diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index 02cf0a54d..e13dcd55f 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -66,7 +66,7 @@ foreign user32 { 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 --- + GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> INT --- TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL --- DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT --- @@ -142,7 +142,7 @@ foreign user32 { 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, y: INT, nReserved: INT, hWnd: HWND, prcRect: ^RECT) -> BOOL --- + TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x, y: INT, nReserved: INT, hWnd: HWND, prcRect: ^RECT) -> INT --- RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT --- CreateAcceleratorTableW :: proc(paccel: LPACCEL, cAccel: INT) -> HACCEL --- @@ -305,6 +305,13 @@ foreign user32 { GetProcessWindowStation :: proc() -> HWINSTA --- GetUserObjectInformationW :: proc(hObj: HANDLE, nIndex: GetUserObjectInformationFlags, pvInfo: PVOID, nLength: DWORD, lpnLengthNeeded: LPDWORD) -> BOOL --- + + OpenClipboard :: proc(hWndNewOwner: HWND) -> BOOL --- + CloseClipboard :: proc() -> BOOL --- + GetClipboardData :: proc(uFormat: UINT) -> HANDLE --- + SetClipboardData :: proc(uFormat: UINT, hMem: HANDLE) -> HANDLE --- + IsClipboardFormatAvailable :: proc(format: UINT) -> BOOL --- + EmptyClipboard :: proc() -> BOOL --- } CreateWindowW :: #force_inline proc "system" ( @@ -373,8 +380,9 @@ 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 +// Retrieves the input code from wParam in WM_INPUT message. +GET_RAWINPUT_CODE_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> RAWINPUT_CODE { + return RAWINPUT_CODE(wParam & 0xFF) } MAKEINTRESOURCEW :: #force_inline proc "contextless" (#any_int i: int) -> LPWSTR { @@ -398,6 +406,16 @@ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE :: DPI_AWARENESS_CONTEXT(~uintptr(2)) DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :: DPI_AWARENESS_CONTEXT(~uintptr(3)) // -4 DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED :: DPI_AWARENESS_CONTEXT(~uintptr(4)) // -5 +RAWINPUT_CODE :: enum { + // The input is in the regular message flow, + // the app is required to call DefWindowProc + // so that the system can perform clean ups. + RIM_INPUT = 0, + // The input is sink only. The app is expected + // to behave nicely. + RIM_INPUTSINK = 1, +} + RAWINPUTHEADER :: struct { dwType: DWORD, dwSize: DWORD, @@ -735,3 +753,31 @@ WinEventFlag :: enum DWORD { SKIPOWNPROCESS = 1, INCONTEXT = 2, } + +// Standard Clipboard Formats +CF_TEXT :: 1 +CF_BITMAP :: 2 +CF_METAFILEPICT :: 3 +CF_SYLK :: 4 +CF_DIF :: 5 +CF_TIFF :: 6 +CF_OEMTEXT :: 7 +CF_DIB :: 8 +CF_PALETTE :: 9 +CF_PENDATA :: 10 +CF_RIFF :: 11 +CF_WAVE :: 12 +CF_UNICODETEXT :: 13 +CF_ENHMETAFILE :: 14 +CF_HDROP :: 15 +CF_LOCALE :: 16 +CF_DIBV5 :: 17 +CF_DSPBITMAP :: 0x0082 +CF_DSPENHMETAFILE :: 0x008E +CF_DSPMETAFILEPICT :: 0x0083 +CF_DSPTEXT :: 0x0081 +CF_GDIOBJFIRST :: 0x0300 +CF_GDIOBJLAST :: 0x03FF +CF_OWNERDISPLAY :: 0x0080 +CF_PRIVATEFIRST :: 0x0200 +CF_PRIVATELAST :: 0x02FF diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index 8882dad71..d3df3b815 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -213,6 +213,7 @@ ERROR_BROKEN_PIPE : DWORD : 109 ERROR_CALL_NOT_IMPLEMENTED : DWORD : 120 ERROR_INSUFFICIENT_BUFFER : DWORD : 122 ERROR_INVALID_NAME : DWORD : 123 +ERROR_NEGATIVE_SEEK : DWORD : 131 ERROR_BAD_ARGUMENTS : DWORD : 160 ERROR_LOCK_FAILED : DWORD : 167 ERROR_ALREADY_EXISTS : DWORD : 183 diff --git a/core/testing/events.odin b/core/testing/events.odin index bab35aaad..c9c4b0271 100644 --- a/core/testing/events.odin +++ b/core/testing/events.odin @@ -1,6 +1,14 @@ //+private package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + import "base:runtime" import "core:sync/chan" import "core:time" diff --git a/core/testing/logging.odin b/core/testing/logging.odin index f1e75d33c..1c3fc4603 100644 --- a/core/testing/logging.odin +++ b/core/testing/logging.odin @@ -1,9 +1,18 @@ //+private package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Ginger Bill: Initial implementation. + Feoramund: Total rewrite. +*/ + import "base:runtime" import "core:fmt" -import pkg_log "core:log" +import "core:log" import "core:strings" import "core:sync/chan" import "core:time" @@ -72,9 +81,9 @@ format_log_text :: proc(level: runtime.Logger_Level, text: string, options: runt backing: [1024]byte buf := strings.builder_from_bytes(backing[:]) - pkg_log.do_level_header(options, &buf, level) - pkg_log.do_time_header(options, &buf, at_time) - pkg_log.do_location_header(options, &buf, location) + log.do_level_header(options, &buf, level) + log.do_time_header(options, &buf, at_time) + log.do_location_header(options, &buf, location) return fmt.aprintf("%s%s", strings.to_string(buf), text, allocator = allocator) } diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index 92e144ccc..81f1d0646 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -1,6 +1,14 @@ //+private package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + import "base:runtime" import "core:encoding/ansi" import "core:fmt" diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 16967e3c7..386ba8cb5 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -1,6 +1,15 @@ //+private package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Ginger Bill: Initial implementation. + Feoramund: Total rewrite. +*/ + import "base:intrinsics" import "base:runtime" import "core:bytes" @@ -9,7 +18,7 @@ import "core:encoding/ansi" @require import "core:encoding/json" import "core:fmt" import "core:io" -@require import pkg_log "core:log" +@require import "core:log" import "core:math/rand" import "core:mem" import "core:os" @@ -25,6 +34,8 @@ TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true) // Always report how much memory is used, even when there are no leaks or bad frees. ALWAYS_REPORT_MEMORY : bool : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false) +// Treat memory leaks and bad frees as errors. +FAIL_ON_BAD_MEMORY : bool : #config(ODIN_TEST_FAIL_ON_BAD_MEMORY, false) // Specify how much memory each thread allocator starts with. PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. @@ -42,24 +53,20 @@ PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) // If it is unspecified, it will be set to the system cycle counter at startup. SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) // Set the lowest log level for this test run. -LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info") +LOG_LEVEL_DEFAULT : string : "debug" when ODIN_DEBUG else "info" +LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT) // 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 { - // Always use .Debug in `-debug` mode. - return .Debug - } else { - when LOG_LEVEL == "debug" { return .Debug } else - when LOG_LEVEL == "info" { return .Info } else - when LOG_LEVEL == "warning" { return .Warning } else - when LOG_LEVEL == "error" { return .Error } else - when LOG_LEVEL == "fatal" { return .Fatal } else { - #panic("Unknown `ODIN_TEST_LOG_LEVEL`: \"" + LOG_LEVEL + "\", possible levels are: \"debug\", \"info\", \"warning\", \"error\", or \"fatal\".") - } + when LOG_LEVEL == "debug" { return .Debug } else + when LOG_LEVEL == "info" { return .Info } else + when LOG_LEVEL == "warning" { return .Warning } else + when LOG_LEVEL == "error" { return .Error } else + when LOG_LEVEL == "fatal" { return .Fatal } else { + #panic("Unknown `ODIN_TEST_LOG_LEVEL`: \"" + LOG_LEVEL + "\", possible levels are: \"debug\", \"info\", \"warning\", \"error\", or \"fatal\".") } } @@ -86,10 +93,19 @@ end_t :: proc(t: ^T) { t.cleanups = {} } -Task_Data :: struct { - it: Internal_Test, - t: T, - allocator_index: int, +when TRACKING_MEMORY && FAIL_ON_BAD_MEMORY { + Task_Data :: struct { + it: Internal_Test, + t: T, + allocator_index: int, + tracking_allocator: ^mem.Tracking_Allocator, + } +} else { + Task_Data :: struct { + it: Internal_Test, + t: T, + allocator_index: int, + } } Task_Timeout :: struct { @@ -133,6 +149,31 @@ run_test_task :: proc(task: thread.Task) { end_t(&data.t) + when TRACKING_MEMORY && FAIL_ON_BAD_MEMORY { + // NOTE(Feoramund): The simplest way to handle treating memory failures + // as errors is to allow the test task runner to access the tracking + // allocator itself. + // + // This way, it's still able to send up a log message, which will be + // used in the end summary, and it can set the test state to `Failed` + // under the usual conditions. + // + // No outside intervention needed. + memory_leaks := len(data.tracking_allocator.allocation_map) + bad_frees := len(data.tracking_allocator.bad_free_array) + + memory_is_in_bad_state := memory_leaks + bad_frees > 0 + + data.t.error_count += memory_leaks + bad_frees + + if memory_is_in_bad_state { + log.errorf("Memory failure in `%s.%s` with %i leak%s and %i bad free%s.", + data.it.pkg, data.it.name, + memory_leaks, "" if memory_leaks == 1 else "s", + bad_frees, "" if bad_frees == 1 else "s") + } + } + new_state : Test_State = .Failed if failed(&data.t) else .Successful chan.send(data.t.channel, Event_State_Change { @@ -418,6 +459,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { #no_bounds_check when TRACKING_MEMORY { task_allocator := mem.tracking_allocator(&task_memory_trackers[task_index]) + when FAIL_ON_BAD_MEMORY { + data.tracking_allocator = &task_memory_trackers[task_index] + } } else { task_allocator := mem.rollback_stack_allocator(&task_allocators[task_index]) } @@ -442,30 +486,35 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } when TEST_THREADS == 0 { - pkg_log.infof("Starting test runner with %i thread%s. Set with -define:ODIN_TEST_THREADS=n.", + log.infof("Starting test runner with %i thread%s. Set with -define:ODIN_TEST_THREADS=n.", thread_count, "" if thread_count == 1 else "s") } else { - pkg_log.infof("Starting test runner with %i thread%s.", + log.infof("Starting test runner with %i thread%s.", thread_count, "" if thread_count == 1 else "s") } when SHARED_RANDOM_SEED == 0 { - pkg_log.infof("The random seed sent to every test is: %v. Set with -define:ODIN_TEST_RANDOM_SEED=n.", shared_random_seed) + log.infof("The random seed sent to every test is: %v. Set with -define:ODIN_TEST_RANDOM_SEED=n.", shared_random_seed) } else { - pkg_log.infof("The random seed sent to every test is: %v.", shared_random_seed) + log.infof("The random seed sent to every test is: %v.", shared_random_seed) } when TRACKING_MEMORY { when ALWAYS_REPORT_MEMORY { - pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") } else { - pkg_log.info("Memory tracking is enabled. Tests will log their memory usage if there's an issue.") + log.info("Memory tracking is enabled. Tests will log their memory usage if there's an issue.") + } + log.info("< Final Mem/ Total Mem> < Peak Mem> (#Free/Alloc) :: [package.test_name]") + } else { + when ALWAYS_REPORT_MEMORY { + log.warn("ODIN_TEST_ALWAYS_REPORT_MEMORY is true, but ODIN_TEST_TRACK_MEMORY is false.") + } + when FAIL_ON_BAD_MEMORY { + log.warn("ODIN_TEST_FAIL_ON_BAD_MEMORY is true, but ODIN_TEST_TRACK_MEMORY is false.") } - pkg_log.info("< Final Mem/ Total Mem> < Peak Mem> (#Free/Alloc) :: [package.test_name]") - } else when ALWAYS_REPORT_MEMORY { - pkg_log.warn("ODIN_TEST_ALWAYS_REPORT_MEMORY is true, but ODIN_TRACK_MEMORY is false.") } start_time := time.now() @@ -507,7 +556,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { if should_report { write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) - pkg_log.log(.Warning if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer)) + when FAIL_ON_BAD_MEMORY { + log.log(.Error if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer)) + } else { + log.log(.Warning if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer)) + } bytes.buffer_reset(&batch_buffer) } @@ -554,7 +607,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } when ODIN_DEBUG { - pkg_log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state) + log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state) } pkg.last_change_state = event.new_state @@ -691,7 +744,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // the signal won't be very useful, whereas asserts and panics // will provide a user-written error message. failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator) - pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) + log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) } when FANCY_OUTPUT { diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin index 0b06852ce..047ea0b3a 100644 --- a/core/testing/signal_handler.odin +++ b/core/testing/signal_handler.odin @@ -1,8 +1,16 @@ //+private package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + import "base:runtime" -import pkg_log "core:log" +import "core:log" Stop_Reason :: enum { Unknown, @@ -13,7 +21,7 @@ Stop_Reason :: enum { } test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { - pkg_log.fatalf("%s: %s", prefix, message, location = loc) + log.fatalf("%s: %s", prefix, message, location = loc) runtime.trap() } diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index d89d84fae..27d1a0735 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -2,6 +2,14 @@ //+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + import "base:intrinsics" import "core:c/libc" import "core:encoding/ansi" diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin index 04981f5af..d6d494fa4 100644 --- a/core/testing/signal_handler_other.odin +++ b/core/testing/signal_handler_other.odin @@ -1,7 +1,21 @@ //+private -//+build !windows !linux !darwin !freebsd !openbsd !netbsd !haiku +//+build !windows +//+build !linux +//+build !darwin +//+build !freebsd +//+build !openbsd +//+build !netbsd +//+build !haiku package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + _setup_signal_handler :: proc() { // Do nothing. } diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 29fe853ef..d5e7c6830 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -1,13 +1,25 @@ package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Ginger Bill: Initial implementation. + Feoramund: Total rewrite. +*/ + import "base:intrinsics" import "base:runtime" -import pkg_log "core:log" +import "core:log" import "core:reflect" +import "core:sync" import "core:sync/chan" import "core:time" +import "core:mem" _ :: reflect // alias reflect to nothing to force visibility for -vet +_ :: mem // in case TRACKING_MEMORY is not enabled // IMPORTANT NOTE: Compiler requires this layout Test_Signature :: proc(^T) @@ -52,18 +64,8 @@ T :: struct { } -@(deprecated="prefer `log.error`") -error :: proc(t: ^T, args: ..any, loc := #caller_location) { - pkg_log.error(..args, location = loc) -} - -@(deprecated="prefer `log.errorf`") -errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - pkg_log.errorf(format, ..args, location = loc) -} - fail :: proc(t: ^T, loc := #caller_location) { - pkg_log.error("FAIL", location=loc) + log.error("FAIL", location=loc) } // fail_now will cause a test to immediately fail and abort, much in the same @@ -75,9 +77,9 @@ fail :: proc(t: ^T, loc := #caller_location) { fail_now :: proc(t: ^T, msg := "", loc := #caller_location) -> ! { t._fail_now_called = true if msg != "" { - pkg_log.error("FAIL:", msg, location=loc) + log.error("FAIL:", msg, location=loc) } else { - pkg_log.error("FAIL", location=loc) + log.error("FAIL", location=loc) } runtime.trap() } @@ -86,17 +88,6 @@ failed :: proc(t: ^T) -> bool { return t.error_count != 0 } -@(deprecated="prefer `log.info`") -log :: proc(t: ^T, args: ..any, loc := #caller_location) { - pkg_log.info(..args, location = loc) -} - -@(deprecated="prefer `log.infof`") -logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - pkg_log.infof(format, ..args, location = loc) -} - - // cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete. // Cleanup procedures will be called in LIFO (last added, first called) order. // @@ -116,14 +107,14 @@ cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) { expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool { if !ok { - pkg_log.error(msg, location=loc) + log.error(msg, location=loc) } return ok } expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool { if !ok { - pkg_log.errorf(format, ..args, location=loc) + log.errorf(format, ..args, location=loc) } return ok } @@ -131,11 +122,28 @@ expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_loc expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) if !ok { - pkg_log.errorf("expected %v, got %v", expected, value, location=loc) + log.errorf("expected %v, got %v", expected, value, location=loc) } return ok } +Memory_Verifier_Proc :: #type proc(t: ^T, ta: ^mem.Tracking_Allocator) + +expect_leaks :: proc(t: ^T, client_test: proc(t: ^T), verifier: Memory_Verifier_Proc) { + when TRACKING_MEMORY { + client_test(t) + ta := (^mem.Tracking_Allocator)(context.allocator.data) + + sync.mutex_lock(&ta.mutex) + // The verifier can inspect this local tracking allocator. + // And then call `testing.expect_*` as makes sense for the client test. + verifier(t, ta) + sync.mutex_unlock(&ta.mutex) + + clear(&ta.bad_free_array) + free_all(context.allocator) + } +} set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { chan.send(t.channel, Event_Set_Fail_Timeout { diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index a4f8c06b9..49adad4d9 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -1,10 +1,9 @@ -package text_edit - /* - Based off the articles by rxi: - * https://rxi.github.io/textbox_behaviour.html - * https://rxi.github.io/a_simple_undo_system.html +Based off the articles by rxi: +- [[ https://rxi.github.io/textbox_behaviour.html ]] +- [[ https://rxi.github.io/a_simple_undo_system.html ]] */ +package text_edit import "base:runtime" import "core:time" @@ -137,7 +136,7 @@ clear_all :: proc(s: ^State) -> (cleared: bool) { // push current text state to the wanted undo|redo stack undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator_Error { - if s.builder != nil { + if s.builder == nil { return nil } text := string(s.builder.buf[:]) diff --git a/core/text/regex/common/common.odin b/core/text/regex/common/common.odin new file mode 100644 index 000000000..4a303e0a3 --- /dev/null +++ b/core/text/regex/common/common.odin @@ -0,0 +1,46 @@ +// This package helps break dependency cycles. +package regex_common + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +// VM limitations +MAX_CAPTURE_GROUPS :: max(#config(ODIN_REGEX_MAX_CAPTURE_GROUPS, 10), 10) +MAX_PROGRAM_SIZE :: int(max(i16)) +MAX_CLASSES :: int(max(u8)) + +Flag :: enum u8 { + // Global: try to match the pattern anywhere in the string. + Global, + // Multiline: treat `^` and `$` as if they also match newlines. + Multiline, + // Case Insensitive: treat `a-z` as if it was also `A-Z`. + Case_Insensitive, + // Ignore Whitespace: bypass unescaped whitespace outside of classes. + Ignore_Whitespace, + // Unicode: let the compiler and virtual machine know to expect Unicode strings. + Unicode, + + // No Capture: avoid saving capture group data entirely. + No_Capture, + // No Optimization: do not pass the pattern through the optimizer; for debugging. + No_Optimization, +} + +Flags :: bit_set[Flag; u8] + +@(rodata) +Flag_To_Letter := #sparse[Flag]u8 { + .Global = 'g', + .Multiline = 'm', + .Case_Insensitive = 'i', + .Ignore_Whitespace = 'x', + .Unicode = 'u', + .No_Capture = 'n', + .No_Optimization = '-', +} diff --git a/core/text/regex/common/debugging.odin b/core/text/regex/common/debugging.odin new file mode 100644 index 000000000..0e4161a92 --- /dev/null +++ b/core/text/regex/common/debugging.odin @@ -0,0 +1,33 @@ +package regex_common + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +@require import "core:os" +import "core:io" +import "core:strings" + +ODIN_DEBUG_REGEX :: #config(ODIN_DEBUG_REGEX, false) + +when ODIN_DEBUG_REGEX { + debug_stream := os.stream_from_handle(os.stderr) +} + +write_padded_hex :: proc(w: io.Writer, #any_int n, zeroes: int) { + sb := strings.builder_make() + defer strings.builder_destroy(&sb) + + sbw := strings.to_writer(&sb) + io.write_int(sbw, n, 0x10) + + io.write_string(w, "0x") + for _ in 0... + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:text/regex/common" +import "core:text/regex/parser" +import "core:text/regex/tokenizer" +import "core:text/regex/virtual_machine" +import "core:unicode" + +Token :: tokenizer.Token +Token_Kind :: tokenizer.Token_Kind +Tokenizer :: tokenizer.Tokenizer + +Rune_Class_Range :: parser.Rune_Class_Range +Rune_Class_Data :: parser.Rune_Class_Data + +Node :: parser.Node +Node_Rune :: parser.Node_Rune +Node_Rune_Class :: parser.Node_Rune_Class +Node_Wildcard :: parser.Node_Wildcard +Node_Concatenation :: parser.Node_Concatenation +Node_Alternation :: parser.Node_Alternation +Node_Repeat_Zero :: parser.Node_Repeat_Zero +Node_Repeat_Zero_Non_Greedy :: parser.Node_Repeat_Zero_Non_Greedy +Node_Repeat_One :: parser.Node_Repeat_One +Node_Repeat_One_Non_Greedy :: parser.Node_Repeat_One_Non_Greedy +Node_Repeat_N :: parser.Node_Repeat_N +Node_Optional :: parser.Node_Optional +Node_Optional_Non_Greedy :: parser.Node_Optional_Non_Greedy +Node_Group :: parser.Node_Group +Node_Anchor :: parser.Node_Anchor +Node_Word_Boundary :: parser.Node_Word_Boundary +Node_Match_All_And_Escape :: parser.Node_Match_All_And_Escape + +Opcode :: virtual_machine.Opcode +Program :: [dynamic]Opcode + +JUMP_SIZE :: size_of(Opcode) + 1 * size_of(u16) +SPLIT_SIZE :: size_of(Opcode) + 2 * size_of(u16) + + +Compiler :: struct { + flags: common.Flags, + class_data: [dynamic]Rune_Class_Data, +} + + +Error :: enum { + None, + Program_Too_Big, + Too_Many_Classes, +} + +classes_are_exact :: proc(q, w: ^Rune_Class_Data) -> bool #no_bounds_check { + assert(q != nil) + assert(w != nil) + + if q == w { + return true + } + + if len(q.runes) != len(w.runes) || len(q.ranges) != len(w.ranges) { + return false + } + + for r, i in q.runes { + if r != w.runes[i] { + return false + } + } + + for r, i in q.ranges { + if r.lower != w.ranges[i].lower || r.upper != w.ranges[i].upper { + return false + } + } + + return true +} + +map_all_classes :: proc(tree: Node, collection: ^[dynamic]Rune_Class_Data) { + if tree == nil { + return + } + + switch specific in tree { + case ^Node_Rune: break + case ^Node_Wildcard: break + case ^Node_Anchor: break + case ^Node_Word_Boundary: break + case ^Node_Match_All_And_Escape: break + + case ^Node_Concatenation: + for subnode in specific.nodes { + map_all_classes(subnode, collection) + } + + case ^Node_Repeat_Zero: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_Zero_Non_Greedy: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_One: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_One_Non_Greedy: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_N: + map_all_classes(specific.inner, collection) + case ^Node_Optional: + map_all_classes(specific.inner, collection) + case ^Node_Optional_Non_Greedy: + map_all_classes(specific.inner, collection) + case ^Node_Group: + map_all_classes(specific.inner, collection) + + case ^Node_Alternation: + map_all_classes(specific.left, collection) + map_all_classes(specific.right, collection) + + case ^Node_Rune_Class: + unseen := true + for &value in collection { + if classes_are_exact(&specific.data, &value) { + unseen = false + break + } + } + + if unseen { + append(collection, specific.data) + } + } +} + +append_raw :: #force_inline proc(code: ^Program, data: $T) { + // NOTE: This is system-dependent endian. + for b in transmute([size_of(T)]byte)data { + append(code, cast(Opcode)b) + } +} +inject_raw :: #force_inline proc(code: ^Program, start: int, data: $T) { + // NOTE: This is system-dependent endian. + for b, i in transmute([size_of(T)]byte)data { + inject_at(code, start + i, cast(Opcode)b) + } +} + +@require_results +generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) { + if node == nil { + return + } + + // NOTE: For Jump/Split arguments, we write as i16 and will reinterpret + // this later when relative jumps are turned into absolute jumps. + + switch specific in node { + // Atomic Nodes: + case ^Node_Rune: + if .Unicode not_in c.flags || specific.data < unicode.MAX_LATIN1 { + append(&code, Opcode.Byte) + append(&code, cast(Opcode)specific.data) + } else { + append(&code, Opcode.Rune) + append_raw(&code, specific.data) + } + + case ^Node_Rune_Class: + if specific.negating { + append(&code, Opcode.Rune_Class_Negated) + } else { + append(&code, Opcode.Rune_Class) + } + + index := -1 + for &data, i in c.class_data { + if classes_are_exact(&data, &specific.data) { + index = i + break + } + } + assert(index != -1, "Unable to find collected Rune_Class_Data index.") + + append(&code, Opcode(index)) + + case ^Node_Wildcard: + append(&code, Opcode.Wildcard) + + case ^Node_Anchor: + if .Multiline in c.flags { + append(&code, Opcode.Multiline_Open) + append(&code, Opcode.Multiline_Close) + } else { + if specific.start { + append(&code, Opcode.Assert_Start) + } else { + append(&code, Opcode.Assert_End) + } + } + case ^Node_Word_Boundary: + if specific.non_word { + append(&code, Opcode.Assert_Non_Word_Boundary) + } else { + append(&code, Opcode.Assert_Word_Boundary) + } + + // Compound Nodes: + case ^Node_Group: + code = generate_code(c, specific.inner) + + if specific.capture && .No_Capture not_in c.flags { + inject_at(&code, 0, Opcode.Save) + inject_at(&code, 1, Opcode(2 * specific.capture_id)) + + append(&code, Opcode.Save) + append(&code, Opcode(2 * specific.capture_id + 1)) + } + + case ^Node_Alternation: + left := generate_code(c, specific.left) + right := generate_code(c, specific.right) + + left_len := len(left) + + // Avoiding duplicate allocation by reusing `left`. + code = left + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + left_len + JUMP_SIZE)) + + append(&code, Opcode.Jump) + append_raw(&code, i16(len(right) + JUMP_SIZE)) + + for opcode in right { + append(&code, opcode) + } + + case ^Node_Concatenation: + for subnode in specific.nodes { + subnode_code := generate_code(c, subnode) + for opcode in subnode_code { + append(&code, opcode) + } + } + + case ^Node_Repeat_Zero: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + original_len + JUMP_SIZE)) + + append(&code, Opcode.Jump) + append_raw(&code, i16(-original_len - SPLIT_SIZE)) + + case ^Node_Repeat_Zero_Non_Greedy: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE + original_len + JUMP_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE)) + + append(&code, Opcode.Jump) + append_raw(&code, i16(-original_len - SPLIT_SIZE)) + + case ^Node_Repeat_One: + code = generate_code(c, specific.inner) + original_len := len(code) + + append(&code, Opcode.Split) + append_raw(&code, i16(-original_len)) + append_raw(&code, i16(SPLIT_SIZE)) + + case ^Node_Repeat_One_Non_Greedy: + code = generate_code(c, specific.inner) + original_len := len(code) + + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE)) + append_raw(&code, i16(-original_len)) + + case ^Node_Repeat_N: + inside := generate_code(c, specific.inner) + original_len := len(inside) + + if specific.lower == specific.upper { // {N} + // e{N} ... evaluates to ... e^N + for i := 0; i < specific.upper; i += 1 { + for opcode in inside { + append(&code, opcode) + } + } + + } else if specific.lower == -1 && specific.upper > 0 { // {,M} + // e{,M} ... evaluates to ... e?^M + for i := 0; i < specific.upper; i += 1 { + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE)) + append_raw(&code, i16(SPLIT_SIZE + original_len)) + for opcode in inside { + append(&code, opcode) + } + } + + } else if specific.lower >= 0 && specific.upper == -1 { // {N,} + // e{N,} ... evaluates to ... e^N e* + for i := 0; i < specific.lower; i += 1 { + for opcode in inside { + append(&code, opcode) + } + } + + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE)) + append_raw(&code, i16(SPLIT_SIZE + original_len + JUMP_SIZE)) + + for opcode in inside { + append(&code, opcode) + } + + append(&code, Opcode.Jump) + append_raw(&code, i16(-original_len - SPLIT_SIZE)) + + } else if specific.lower >= 0 && specific.upper > 0 { + // e{N,M} evaluates to ... e^N e?^(M-N) + for i := 0; i < specific.lower; i += 1 { + for opcode in inside { + append(&code, opcode) + } + } + for i := 0; i < specific.upper - specific.lower; i += 1 { + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE + original_len)) + append_raw(&code, i16(SPLIT_SIZE)) + for opcode in inside { + append(&code, opcode) + } + } + + } else { + panic("RegEx compiler received invalid repetition group.") + } + + case ^Node_Optional: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + original_len)) + + case ^Node_Optional_Non_Greedy: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE + original_len)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE)) + + case ^Node_Match_All_And_Escape: + append(&code, Opcode.Match_All_And_Escape) + } + + return +} + +@require_results +compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: [dynamic]Rune_Class_Data, err: Error) { + if tree == nil { + if .No_Capture not_in flags { + append(&code, Opcode.Save); append(&code, Opcode(0x00)) + append(&code, Opcode.Save); append(&code, Opcode(0x01)) + append(&code, Opcode.Match) + } else { + append(&code, Opcode.Match_And_Exit) + } + return + } + + c: Compiler + c.flags = flags + + map_all_classes(tree, &class_data) + if len(class_data) >= common.MAX_CLASSES { + err = .Too_Many_Classes + return + } + c.class_data = class_data + + code = generate_code(&c, tree) + + pc_open := 0 + + add_global: if .Global in flags { + // Check if the opening to the pattern is predictable. + // If so, use one of the optimized Wait opcodes. + iter := virtual_machine.Opcode_Iterator{ code[:], 0 } + seek_loop: for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + #partial switch opcode { + case .Byte: + inject_at(&code, pc_open, Opcode.Wait_For_Byte) + pc_open += size_of(Opcode) + inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) + pc_open += size_of(u8) + break add_global + + case .Rune: + operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1]) + inject_at(&code, pc_open, Opcode.Wait_For_Rune) + pc_open += size_of(Opcode) + inject_raw(&code, pc_open, operand) + pc_open += size_of(rune) + break add_global + + case .Rune_Class: + inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class) + pc_open += size_of(Opcode) + inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) + pc_open += size_of(u8) + break add_global + + case .Rune_Class_Negated: + inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated) + pc_open += size_of(Opcode) + inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) + pc_open += size_of(u8) + break add_global + + case .Save: + continue + case: + break seek_loop + } + } + + // `.*?` + inject_at(&code, pc_open, Opcode.Split) + pc_open += size_of(byte) + inject_raw(&code, pc_open, i16(SPLIT_SIZE + size_of(byte) + JUMP_SIZE)) + pc_open += size_of(i16) + inject_raw(&code, pc_open, i16(SPLIT_SIZE)) + pc_open += size_of(i16) + + inject_at(&code, pc_open, Opcode.Wildcard) + pc_open += size_of(byte) + + inject_at(&code, pc_open, Opcode.Jump) + pc_open += size_of(byte) + inject_raw(&code, pc_open, i16(-size_of(byte) - SPLIT_SIZE)) + pc_open += size_of(i16) + + } + + if .No_Capture not_in flags { + // `(` + inject_at(&code, pc_open, Opcode.Save) + inject_at(&code, pc_open + size_of(byte), Opcode(0x00)) + + // `)` + append(&code, Opcode.Save); append(&code, Opcode(0x01)) + + append(&code, Opcode.Match) + } else { + append(&code, Opcode.Match_And_Exit) + } + + if len(code) >= common.MAX_PROGRAM_SIZE { + err = .Program_Too_Big + return + } + + // NOTE: No further opcode addition beyond this point, as we've already + // checked the program size. Removal or transformation is fine. + + // Post-Compile Optimizations: + + // * Jump Extension + // + // A:RelJmp(1) -> B:RelJmp(2) => A:RelJmp(2) + if .No_Optimization not_in flags { + for passes_left := 1; passes_left > 0; passes_left -= 1 { + do_another_pass := false + + iter := virtual_machine.Opcode_Iterator{ code[:], 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + #partial switch opcode { + case .Jump: + jmp := cast(^i16)&code[pc+size_of(Opcode)] + jmp_value := intrinsics.unaligned_load(jmp) + if code[cast(i16)pc+jmp_value] == .Jump { + next_jmp := intrinsics.unaligned_load(cast(^i16)&code[cast(i16)pc+jmp_value+size_of(Opcode)]) + intrinsics.unaligned_store(jmp, jmp_value + next_jmp) + do_another_pass = true + } + case .Split: + jmp_x := cast(^i16)&code[pc+size_of(Opcode)] + jmp_x_value := intrinsics.unaligned_load(jmp_x) + if code[cast(i16)pc+jmp_x_value] == .Jump { + next_jmp := intrinsics.unaligned_load(cast(^i16)&code[cast(i16)pc+jmp_x_value+size_of(Opcode)]) + intrinsics.unaligned_store(jmp_x, jmp_x_value + next_jmp) + do_another_pass = true + } + jmp_y := cast(^i16)&code[pc+size_of(Opcode)+size_of(i16)] + jmp_y_value := intrinsics.unaligned_load(jmp_y) + if code[cast(i16)pc+jmp_y_value] == .Jump { + next_jmp := intrinsics.unaligned_load(cast(^i16)&code[cast(i16)pc+jmp_y_value+size_of(Opcode)]) + intrinsics.unaligned_store(jmp_y, jmp_y_value + next_jmp) + do_another_pass = true + } + } + } + + if do_another_pass { + passes_left += 1 + } + } + } + + // * Relative Jump to Absolute Jump + // + // RelJmp{PC +/- N} => AbsJmp{M} + iter := virtual_machine.Opcode_Iterator{ code[:], 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + // NOTE: The virtual machine implementation depends on this. + #partial switch opcode { + case .Jump: + jmp := cast(^u16)&code[pc+size_of(Opcode)] + intrinsics.unaligned_store(jmp, intrinsics.unaligned_load(jmp) + cast(u16)pc) + case .Split: + jmp_x := cast(^u16)&code[pc+size_of(Opcode)] + intrinsics.unaligned_store(jmp_x, intrinsics.unaligned_load(jmp_x) + cast(u16)pc) + jmp_y := cast(^u16)&code[pc+size_of(Opcode)+size_of(i16)] + intrinsics.unaligned_store(jmp_y, intrinsics.unaligned_load(jmp_y) + cast(u16)pc) + } + } + + return +} diff --git a/core/text/regex/compiler/debugging.odin b/core/text/regex/compiler/debugging.odin new file mode 100644 index 000000000..142cb8839 --- /dev/null +++ b/core/text/regex/compiler/debugging.odin @@ -0,0 +1,93 @@ +package regex_compiler + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:io" +import "core:text/regex/common" +import "core:text/regex/virtual_machine" + +get_jump_targets :: proc(code: []Opcode) -> (jump_targets: map[int]int) { + iter := virtual_machine.Opcode_Iterator{ code, 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + #partial switch opcode { + case .Jump: + jmp := cast(int)intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + jump_targets[jmp] = pc + case .Split: + jmp_x := cast(int)intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + jmp_y := cast(int)intrinsics.unaligned_load(cast(^u16)&code[pc+3]) + jump_targets[jmp_x] = pc + jump_targets[jmp_y] = pc + } + } + return +} + +trace :: proc(w: io.Writer, code: []Opcode) { + jump_targets := get_jump_targets(code) + defer delete(jump_targets) + + iter := virtual_machine.Opcode_Iterator{ code, 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + if src, ok := jump_targets[pc]; ok { + io.write_string(w, "--") + common.write_padded_hex(w, src, 4) + io.write_string(w, "--> ") + } else { + io.write_string(w, " ") + } + + io.write_string(w, "[PC: ") + common.write_padded_hex(w, pc, 4) + io.write_string(w, "] ") + io.write_string(w, virtual_machine.opcode_to_name(opcode)) + io.write_byte(w, ' ') + + #partial switch opcode { + case .Byte: + operand := cast(rune)code[pc+1] + io.write_encoded_rune(w, operand) + case .Rune: + operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1]) + io.write_encoded_rune(w, operand) + case .Rune_Class, .Rune_Class_Negated: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + case .Jump: + jmp := intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + io.write_string(w, "-> $") + common.write_padded_hex(w, jmp, 4) + case .Split: + jmp_x := intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + jmp_y := intrinsics.unaligned_load(cast(^u16)&code[pc+3]) + io.write_string(w, "=> $") + common.write_padded_hex(w, jmp_x, 4) + io.write_string(w, ", $") + common.write_padded_hex(w, jmp_y, 4) + case .Save: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + case .Wait_For_Byte: + operand := cast(rune)code[pc+1] + io.write_encoded_rune(w, operand) + case .Wait_For_Rune: + operand := (cast(^rune)&code[pc+1])^ + io.write_encoded_rune(w, operand) + case .Wait_For_Rune_Class: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + case .Wait_For_Rune_Class_Negated: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + } + + io.write_byte(w, '\n') + } +} diff --git a/core/text/regex/compiler/doc.odin b/core/text/regex/compiler/doc.odin new file mode 100644 index 000000000..8c876d837 --- /dev/null +++ b/core/text/regex/compiler/doc.odin @@ -0,0 +1,9 @@ +/* +package regex_compiler implements a bytecode compiler for the virtual machine +included alongside it. + +Operands larger than u8 are written in system endian order. + +More details can be found in the documentation for the virtual machine. +*/ +package regex_compiler diff --git a/core/text/regex/doc.odin b/core/text/regex/doc.odin new file mode 100644 index 000000000..61ab8b80e --- /dev/null +++ b/core/text/regex/doc.odin @@ -0,0 +1,97 @@ +/* +package regex implements a complete suite for using Regular Expressions to +match and capture text. + +Regular expressions are used to describe how a piece of text can match to +another, using a pattern language. + +Odin's regex library implements the following features: + + Alternation: `apple|cherry` + Classes: `[0-9_]` + Classes, negated: `[^0-9_]` + Shorthands: `\d\s\w` + Shorthands, negated: `\D\S\W` + Wildcards: `.` + Repeat, optional: `a*` + Repeat, at least once: `a+` + Repetition: `a{1,2}` + Optional: `a?` + Group, capture: `([0-9])` + Group, non-capture: `(?:[0-9])` + Start & End Anchors: `^hello$` + Word Boundaries: `\bhello\b` + Non-Word Boundaries: `hello\B` + +These specifiers can be composed together, such as an optional group: +`(?:hello)?` + +This package also supports the non-greedy variants of the repeating and +optional specifiers by appending a `?` to them. + +Of the shorthand classes that are supported, they are all ASCII-based, even +when compiling in Unicode mode. This is for the sake of general performance and +simplicity, as there are thousands of Unicode codepoints which would qualify as +either a digit, space, or word character which could be irrelevant depending on +what is being matched. + +Here are the shorthand class equivalencies: + \d: [0-9] + \s: [\t\n\f\r ] + \w: [0-9A-Z_a-z] + +If you need your own shorthands, you can compose strings together like so: + MY_HEX :: "[0-9A-Fa-f]" + PATTERN :: MY_HEX + "-" + MY_HEX + +The compiler will handle turning multiple identical classes into references to +the same set of matching runes, so there's no penalty for doing it like this. + + + + ``Some people, when confronted with a problem, think + "I know, I'll use regular expressions." Now they have two problems.'' + + - Jamie Zawinski + + +Regular expressions have gathered a reputation over the decades for often being +chosen as the wrong tool for the job. Here, we will clarify a few cases in +which RegEx might be good or bad. + + +**When is it a good time to use RegEx?** + +- You don't know at compile-time what patterns of text the program will need to + match when it's running. +- As an example, you are making a client which can be configured by the user to + trigger on certain text patterns received from a server. +- For another example, you need a way for users of a text editor to compose + matching strings that are more intricate than a simple substring lookup. +- The text you're matching against is small (< 64 KiB) and your patterns aren't + overly complicated with branches (alternations, repeats, and optionals). +- If none of the above general impressions apply but your project doesn't + warrant long-term maintenance. + +**When is it a bad time to use RegEx?** + +- You know at compile-time the grammar you're parsing; a hand-made parser has + the potential to be more maintainable and readable. +- The grammar you're parsing has certain validation steps that lend itself to + forming complicated expressions, such as e-mail addresses, URIs, dates, + postal codes, credit cards, et cetera. Using RegEx to validate these + structures is almost always a bad sign. +- The text you're matching against is big (> 1 MiB); you would be better served + by first dividing the text into manageable chunks and using some heuristic to + locate the most likely location of a match before applying RegEx against it. +- You value high performance and low memory usage; RegEx will always have a + certain overhead which increases with the complexity of the pattern. + + +The implementation of this package has been optimized, but it will never be as +thoroughly performant as a hand-made parser. In comparison, there are just too +many intermediate steps, assumptions, and generalizations in what it takes to +handle a regular expression. + +*/ +package regex diff --git a/core/text/regex/optimizer/doc.odin b/core/text/regex/optimizer/doc.odin new file mode 100644 index 000000000..7f2c84c8d --- /dev/null +++ b/core/text/regex/optimizer/doc.odin @@ -0,0 +1,58 @@ +/* +package regex_optimizer implements an optimizer which acts upon the AST of a +parsed regular expression pattern, transforming it in-place without moving to a +compilation step. + +Where possible, it aims to reduce branching as much as possible in the +expression by reducing usage of `|`. + + +Here is a summary of the optimizations that it will do: + +* Class Simplification : `[aab]` => `[ab]` + `[aa]` => `[a]` + +* Class Reduction : `[a]` => `a` +* Range Construction : `[abc]` => `[a-c]` +* Rune Merging into Range : `[aa-c]` => `[a-c]` + +* Range Merging : `[a-cc-e]` => `[a-e]` + `[a-cd-e]` => `[a-e]` + `[a-cb-e]` => `[a-e]` + +* Alternation to Optional : `a|` => `a?` +* Alternation to Optional Non-Greedy : `|a` => `a??` +* Alternation Reduction : `a|a` => `a` +* Alternation to Class : `a|b` => `[ab]` +* Class Union : `[a0]|[b1]` => `[a0b1]` + `[a-b]|c` => `[a-bc]` + `a|[b-c]` => `[b-ca]` + +* Wildcard Reduction : `a|.` => `.` + `.|a` => `.` + `[ab]|.` => `.` + `.|[ab]` => `.` + +* Common Suffix Elimination : `blueberry|strawberry` => `(?:blue|straw)berry` +* Common Prefix Elimination : `abi|abe` => `ab(?:i|e)` + +* Composition: Consume All to Anchored End + `.*$` => + `.+$` => `.` + + +Possible future improvements: + +- Change the AST of alternations to be a list instead of a tree, so that + constructions such as `(ab|bb|cb)` can be considered in whole by the affix + elimination optimizations. + +- Introduce specialized opcodes for certain classes of repetition. + +- Add Common Infix Elimination. + +- Measure the precise finite minimum and maximum of a pattern, if available, + and check against that on any strings before running the virtual machine. + +*/ +package regex_optimizer diff --git a/core/text/regex/optimizer/optimizer.odin b/core/text/regex/optimizer/optimizer.odin new file mode 100644 index 000000000..e23cc1bc5 --- /dev/null +++ b/core/text/regex/optimizer/optimizer.odin @@ -0,0 +1,530 @@ +package regex_optimizer + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +@require import "core:io" +import "core:slice" +import "core:text/regex/common" +import "core:text/regex/parser" + +Rune_Class_Range :: parser.Rune_Class_Range + +Node :: parser.Node +Node_Rune :: parser.Node_Rune +Node_Rune_Class :: parser.Node_Rune_Class +Node_Wildcard :: parser.Node_Wildcard +Node_Concatenation :: parser.Node_Concatenation +Node_Alternation :: parser.Node_Alternation +Node_Repeat_Zero :: parser.Node_Repeat_Zero +Node_Repeat_Zero_Non_Greedy :: parser.Node_Repeat_Zero_Non_Greedy +Node_Repeat_One :: parser.Node_Repeat_One +Node_Repeat_One_Non_Greedy :: parser.Node_Repeat_One_Non_Greedy +Node_Repeat_N :: parser.Node_Repeat_N +Node_Optional :: parser.Node_Optional +Node_Optional_Non_Greedy :: parser.Node_Optional_Non_Greedy +Node_Group :: parser.Node_Group +Node_Anchor :: parser.Node_Anchor +Node_Word_Boundary :: parser.Node_Word_Boundary +Node_Match_All_And_Escape :: parser.Node_Match_All_And_Escape + + +class_range_sorter :: proc(i, j: Rune_Class_Range) -> bool { + return i.lower < j.lower +} + +optimize_subtree :: proc(tree: Node, flags: common.Flags) -> (result: Node, changes: int) { + if tree == nil { + return nil, 0 + } + + result = tree + + switch specific in tree { + // No direct optimization possible on these nodes: + case ^Node_Rune: break + case ^Node_Wildcard: break + case ^Node_Anchor: break + case ^Node_Word_Boundary: break + case ^Node_Match_All_And_Escape: break + + case ^Node_Concatenation: + // * Composition: Consume All to Anchored End + // + // DO: `.*$` => + // DO: `.+$` => `.` + if .Multiline not_in flags && len(specific.nodes) >= 2 { + i := len(specific.nodes) - 2 + wrza: { + subnode := specific.nodes[i].(^Node_Repeat_Zero) or_break wrza + _ = subnode.inner.(^Node_Wildcard) or_break wrza + next_node := specific.nodes[i+1].(^Node_Anchor) or_break wrza + if next_node.start == false { + specific.nodes[i] = new(Node_Match_All_And_Escape) + ordered_remove(&specific.nodes, i + 1) + changes += 1 + break + } + } + wroa: { + subnode := specific.nodes[i].(^Node_Repeat_One) or_break wroa + subsubnode := subnode.inner.(^Node_Wildcard) or_break wroa + next_node := specific.nodes[i+1].(^Node_Anchor) or_break wroa + if next_node.start == false { + specific.nodes[i] = subsubnode + specific.nodes[i+1] = new(Node_Match_All_And_Escape) + changes += 1 + break + } + } + } + + // Only recursive optimizations: + #no_bounds_check for i := 0; i < len(specific.nodes); i += 1 { + subnode, subnode_changes := optimize_subtree(specific.nodes[i], flags) + changes += subnode_changes + if subnode == nil { + ordered_remove(&specific.nodes, i) + i -= 1 + changes += 1 + } else { + specific.nodes[i] = subnode + } + } + + if len(specific.nodes) == 1 { + result = specific.nodes[0] + changes += 1 + } else if len(specific.nodes) == 0 { + return nil, changes + 1 + } + + case ^Node_Repeat_Zero: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_Zero_Non_Greedy: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_One: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_One_Non_Greedy: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_N: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Optional: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Optional_Non_Greedy: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + + case ^Node_Group: + specific.inner, changes = optimize_subtree(specific.inner, flags) + + if specific.inner == nil { + return nil, changes + 1 + } + + if !specific.capture { + result = specific.inner + changes += 1 + } + + // Full optimization: + case ^Node_Rune_Class: + // * Class Simplification + // + // DO: `[aab]` => `[ab]` + // DO: `[aa]` => `[a]` + runes_seen: map[rune]bool + + for r in specific.runes { + runes_seen[r] = true + } + + if len(runes_seen) != len(specific.runes) { + clear(&specific.runes) + for key in runes_seen { + append(&specific.runes, key) + } + changes += 1 + } + + // * Class Reduction + // + // DO: `[a]` => `a` + if !specific.negating && len(specific.runes) == 1 && len(specific.ranges) == 0 { + only_rune := specific.runes[0] + + node := new(Node_Rune) + node.data = only_rune + + return node, changes + 1 + } + + // * Range Construction + // + // DO: `[abc]` => `[a-c]` + slice.sort(specific.runes[:]) + if len(specific.runes) > 1 { + new_range: Rune_Class_Range + new_range.lower = specific.runes[0] + new_range.upper = specific.runes[0] + + #no_bounds_check for i := 1; i < len(specific.runes); i += 1 { + r := specific.runes[i] + if new_range.lower == -1 { + new_range = { r, r } + continue + } + + if r == new_range.lower - 1 { + new_range.lower -= 1 + ordered_remove(&specific.runes, i) + i -= 1 + changes += 1 + } else if r == new_range.upper + 1 { + new_range.upper += 1 + ordered_remove(&specific.runes, i) + i -= 1 + changes += 1 + } else if new_range.lower != new_range.upper { + append(&specific.ranges, new_range) + new_range = { -1, -1 } + changes += 1 + } + } + + if new_range.lower != new_range.upper { + append(&specific.ranges, new_range) + changes += 1 + } + } + + // * Rune Merging into Range + // + // DO: `[aa-c]` => `[a-c]` + for range in specific.ranges { + #no_bounds_check for i := 0; i < len(specific.runes); i += 1 { + r := specific.runes[i] + if range.lower <= r && r <= range.upper { + ordered_remove(&specific.runes, i) + i -= 1 + changes += 1 + } + } + } + + // * Range Merging + // + // DO: `[a-cc-e]` => `[a-e]` + // DO: `[a-cd-e]` => `[a-e]` + // DO: `[a-cb-e]` => `[a-e]` + slice.sort_by(specific.ranges[:], class_range_sorter) + #no_bounds_check for i := 0; i < len(specific.ranges) - 1; i += 1 { + for j := i + 1; j < len(specific.ranges); j += 1 { + left_range := &specific.ranges[i] + right_range := specific.ranges[j] + + if left_range.upper == right_range.lower || + left_range.upper == right_range.lower - 1 || + left_range.lower <= right_range.lower && right_range.lower <= left_range.upper { + left_range.upper = max(left_range.upper, right_range.upper) + ordered_remove(&specific.ranges, j) + j -= 1 + changes += 1 + } else { + break + } + } + } + + if len(specific.ranges) == 0 { + specific.ranges = {} + } + if len(specific.runes) == 0 { + specific.runes = {} + } + + // * NOP + // + // DO: `[]` => + if len(specific.ranges) + len(specific.runes) == 0 { + return nil, 1 + } + + slice.sort(specific.runes[:]) + slice.sort_by(specific.ranges[:], class_range_sorter) + + case ^Node_Alternation: + // Perform recursive optimization first. + left_changes, right_changes: int + specific.left, left_changes = optimize_subtree(specific.left, flags) + specific.right, right_changes = optimize_subtree(specific.right, flags) + changes += left_changes + right_changes + + // * Alternation to Optional + // + // DO: `a|` => `a?` + if specific.left != nil && specific.right == nil { + node := new(Node_Optional) + node.inner = specific.left + return node, 1 + } + + // * Alternation to Optional Non-Greedy + // + // DO: `|a` => `a??` + if specific.right != nil && specific.left == nil { + node := new(Node_Optional_Non_Greedy) + node.inner = specific.right + return node, 1 + } + + // * NOP + // + // DO: `|` => + if specific.left == nil && specific.right == nil { + return nil, 1 + } + + left_rune, left_is_rune := specific.left.(^Node_Rune) + right_rune, right_is_rune := specific.right.(^Node_Rune) + + if left_is_rune && right_is_rune { + if left_rune.data == right_rune.data { + // * Alternation Reduction + // + // DO: `a|a` => `a` + return left_rune, 1 + } else { + // * Alternation to Class + // + // DO: `a|b` => `[ab]` + node := new(Node_Rune_Class) + append(&node.runes, left_rune.data) + append(&node.runes, right_rune.data) + return node, 1 + } + } + + left_wildcard, left_is_wildcard := specific.left.(^Node_Wildcard) + right_wildcard, right_is_wildcard := specific.right.(^Node_Wildcard) + + // * Class Union + // + // DO: `[a0]|[b1]` => `[a0b1]` + left_class, left_is_class := specific.left.(^Node_Rune_Class) + right_class, right_is_class := specific.right.(^Node_Rune_Class) + if left_is_class && right_is_class { + for r in right_class.runes { + append(&left_class.runes, r) + } + for range in right_class.ranges { + append(&left_class.ranges, range) + } + return left_class, 1 + } + + // * Class Union + // + // DO: `[a-b]|c` => `[a-bc]` + if left_is_class && right_is_rune { + append(&left_class.runes, right_rune.data) + return left_class, 1 + } + + // * Class Union + // + // DO: `a|[b-c]` => `[b-ca]` + if left_is_rune && right_is_class { + append(&right_class.runes, left_rune.data) + return right_class, 1 + } + + // * Wildcard Reduction + // + // DO: `a|.` => `.` + if left_is_rune && right_is_wildcard { + return right_wildcard, 1 + } + + // * Wildcard Reduction + // + // DO: `.|a` => `.` + if left_is_wildcard && right_is_rune { + return left_wildcard, 1 + } + + // * Wildcard Reduction + // + // DO: `[ab]|.` => `.` + if left_is_class && right_is_wildcard { + return right_wildcard, 1 + } + + // * Wildcard Reduction + // + // DO: `.|[ab]` => `.` + if left_is_wildcard && right_is_class { + return left_wildcard, 1 + } + + left_concatenation, left_is_concatenation := specific.left.(^Node_Concatenation) + right_concatenation, right_is_concatenation := specific.right.(^Node_Concatenation) + + // * Common Suffix Elimination + // + // DO: `blueberry|strawberry` => `(?:blue|straw)berry` + if left_is_concatenation && right_is_concatenation { + // Remember that a concatenation could contain any node, not just runes. + left_len := len(left_concatenation.nodes) + right_len := len(right_concatenation.nodes) + least_len := min(left_len, right_len) + same_len := 0 + for i := 1; i <= least_len; i += 1 { + left_subrune, left_is_subrune := left_concatenation.nodes[left_len - i].(^Node_Rune) + right_subrune, right_is_subrune := right_concatenation.nodes[right_len - i].(^Node_Rune) + + if !left_is_subrune || !right_is_subrune { + // One of the nodes isn't a rune; there's nothing more we can do. + break + } + + if left_subrune.data == right_subrune.data { + same_len += 1 + } else { + // No more similarities. + break + } + } + + if same_len > 0 { + // Dissolve this alternation into a concatenation. + cat_node := new(Node_Concatenation) + group_node := new(Node_Group) + append(&cat_node.nodes, group_node) + + // Turn the concatenation into the common suffix. + for i := left_len - same_len; i < left_len; i += 1 { + append(&cat_node.nodes, left_concatenation.nodes[i]) + } + + // Construct the group of alternating prefixes. + for i := same_len; i > 0; i -= 1 { + pop(&left_concatenation.nodes) + pop(&right_concatenation.nodes) + } + + // (Re-using this alternation node.) + alter_node := specific + alter_node.left = left_concatenation + alter_node.right = right_concatenation + group_node.inner = alter_node + + return cat_node, 1 + } + } + + // * Common Prefix Elimination + // + // DO: `abi|abe` => `ab(?:i|e)` + if left_is_concatenation && right_is_concatenation { + // Try to identify a common prefix. + // Remember that a concatenation could contain any node, not just runes. + least_len := min(len(left_concatenation.nodes), len(right_concatenation.nodes)) + same_len := 0 + for i := 0; i < least_len; i += 1 { + left_subrune, left_is_subrune := left_concatenation.nodes[i].(^Node_Rune) + right_subrune, right_is_subrune := right_concatenation.nodes[i].(^Node_Rune) + + if !left_is_subrune || !right_is_subrune { + // One of the nodes isn't a rune; there's nothing more we can do. + break + } + + if left_subrune.data == right_subrune.data { + same_len = i + 1 + } else { + // No more similarities. + break + } + } + + if same_len > 0 { + cat_node := new(Node_Concatenation) + for i := 0; i < same_len; i += 1 { + append(&cat_node.nodes, left_concatenation.nodes[i]) + } + for i := same_len; i > 0; i -= 1 { + ordered_remove(&left_concatenation.nodes, 0) + ordered_remove(&right_concatenation.nodes, 0) + } + + group_node := new(Node_Group) + // (Re-using this alternation node.) + alter_node := specific + alter_node.left = left_concatenation + alter_node.right = right_concatenation + group_node.inner = alter_node + + append(&cat_node.nodes, group_node) + return cat_node, 1 + } + } + } + + return +} + +optimize :: proc(tree: Node, flags: common.Flags) -> (result: Node, changes: int) { + result = tree + new_changes := 0 + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "AST before Optimizer: ") + parser.write_node(common.debug_stream, tree) + io.write_byte(common.debug_stream, '\n') + } + + // Keep optimizing until no more changes are seen. + for { + result, new_changes = optimize_subtree(result, flags) + changes += new_changes + if new_changes == 0 { + break + } + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "AST after Optimizer: ") + parser.write_node(common.debug_stream, result) + io.write_byte(common.debug_stream, '\n') + } + + + return +} diff --git a/core/text/regex/parser/debugging.odin b/core/text/regex/parser/debugging.odin new file mode 100644 index 000000000..e060f58c2 --- /dev/null +++ b/core/text/regex/parser/debugging.odin @@ -0,0 +1,111 @@ +package regex_parser + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:io" + +write_node :: proc(w: io.Writer, node: Node) { + switch specific in node { + case ^Node_Rune: + io.write_rune(w, specific.data) + + case ^Node_Rune_Class: + io.write_byte(w, '[') + if specific.negating { + io.write_byte(w, '^') + } + for r in specific.data.runes { + io.write_rune(w, r) + } + for range in specific.data.ranges { + io.write_rune(w, range.lower) + io.write_byte(w, '-') + io.write_rune(w, range.upper) + } + io.write_byte(w, ']') + + case ^Node_Wildcard: + io.write_byte(w, '.') + + case ^Node_Concatenation: + io.write_rune(w, '「') + for subnode, i in specific.nodes { + if i != 0 { + io.write_rune(w, '⋅') + } + write_node(w, subnode) + } + io.write_rune(w, '」') + + case ^Node_Repeat_Zero: + write_node(w, specific.inner) + io.write_byte(w, '*') + case ^Node_Repeat_Zero_Non_Greedy: + write_node(w, specific.inner) + io.write_string(w, "*?") + case ^Node_Repeat_One: + write_node(w, specific.inner) + io.write_byte(w, '+') + case ^Node_Repeat_One_Non_Greedy: + write_node(w, specific.inner) + io.write_string(w, "+?") + + case ^Node_Repeat_N: + write_node(w, specific.inner) + if specific.lower == 0 && specific.upper == -1 { + io.write_byte(w, '*') + } else if specific.lower == 1 && specific.upper == -1 { + io.write_byte(w, '+') + } else { + io.write_byte(w, '{') + io.write_int(w, specific.lower) + io.write_byte(w, ',') + io.write_int(w, specific.upper) + io.write_byte(w, '}') + } + + case ^Node_Alternation: + io.write_rune(w, '《') + write_node(w, specific.left) + io.write_byte(w, '|') + write_node(w, specific.right) + io.write_rune(w, '》') + + case ^Node_Optional: + io.write_rune(w, '〈') + write_node(w, specific.inner) + io.write_byte(w, '?') + io.write_rune(w, '〉') + case ^Node_Optional_Non_Greedy: + io.write_rune(w, '〈') + write_node(w, specific.inner) + io.write_string(w, "??") + io.write_rune(w, '〉') + + case ^Node_Group: + io.write_byte(w, '(') + if !specific.capture { + io.write_string(w, "?:") + } + write_node(w, specific.inner) + io.write_byte(w, ')') + + case ^Node_Anchor: + io.write_byte(w, '^' if specific.start else '$') + + case ^Node_Word_Boundary: + io.write_string(w, `\B` if specific.non_word else `\b`) + + case ^Node_Match_All_And_Escape: + io.write_string(w, "《.*$》") + + case nil: + io.write_string(w, "") + } +} diff --git a/core/text/regex/parser/doc.odin b/core/text/regex/parser/doc.odin new file mode 100644 index 000000000..f518e518d --- /dev/null +++ b/core/text/regex/parser/doc.odin @@ -0,0 +1,10 @@ +/* +package regex_parser implements a Pratt parser, also known as a Top-Down +Operator Precedence parser, for parsing tokenized regular expression patterns. + +References: +- https://dl.acm.org/doi/10.1145/512927.512931 +- https://tdop.github.io/ +- http://crockford.com/javascript/tdop/tdop.html +*/ +package regex_parser diff --git a/core/text/regex/parser/parser.odin b/core/text/regex/parser/parser.odin new file mode 100644 index 000000000..038d4cb85 --- /dev/null +++ b/core/text/regex/parser/parser.odin @@ -0,0 +1,590 @@ +package regex_parser + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:strconv" +import "core:strings" +import "core:text/regex/common" +import "core:text/regex/tokenizer" +import "core:unicode" +import "core:unicode/utf8" + +Token :: tokenizer.Token +Token_Kind :: tokenizer.Token_Kind +Tokenizer :: tokenizer.Tokenizer + +Rune_Class_Range :: struct { + lower, upper: rune, +} +Rune_Class_Data :: struct { + runes: [dynamic]rune, + ranges: [dynamic]Rune_Class_Range, +} + + +Node_Rune :: struct { + data: rune, +} + +Node_Rune_Class :: struct { + negating: bool, + using data: Rune_Class_Data, +} + +Node_Wildcard :: struct {} + +Node_Alternation :: struct { + left, right: Node, +} + +Node_Concatenation :: struct { + nodes: [dynamic]Node, +} + +Node_Repeat_Zero :: struct { + inner: Node, +} +Node_Repeat_Zero_Non_Greedy :: struct { + inner: Node, +} +Node_Repeat_One :: struct { + inner: Node, +} +Node_Repeat_One_Non_Greedy :: struct { + inner: Node, +} + +Node_Repeat_N :: struct { + inner: Node, + lower, upper: int, +} + +Node_Optional :: struct { + inner: Node, +} +Node_Optional_Non_Greedy :: struct { + inner: Node, +} + +Node_Group :: struct { + inner: Node, + capture_id: int, + capture: bool, +} + +Node_Anchor :: struct { + start: bool, +} +Node_Word_Boundary :: struct { + non_word: bool, +} + +Node_Match_All_And_Escape :: struct {} + +Node :: union { + ^Node_Rune, + ^Node_Rune_Class, + ^Node_Wildcard, + ^Node_Concatenation, + ^Node_Alternation, + ^Node_Repeat_Zero, + ^Node_Repeat_Zero_Non_Greedy, + ^Node_Repeat_One, + ^Node_Repeat_One_Non_Greedy, + ^Node_Repeat_N, + ^Node_Optional, + ^Node_Optional_Non_Greedy, + ^Node_Group, + ^Node_Anchor, + ^Node_Word_Boundary, + + // Optimized nodes (not created by the Parser): + ^Node_Match_All_And_Escape, +} + + +left_binding_power :: proc(kind: Token_Kind) -> int { + #partial switch kind { + case .Alternate: return 1 + case .Concatenate: return 2 + case .Repeat_Zero, .Repeat_One, + .Repeat_Zero_Non_Greedy, .Repeat_One_Non_Greedy, + .Repeat_N: return 3 + case .Optional, + .Optional_Non_Greedy: return 4 + case .Open_Paren, + .Open_Paren_Non_Capture: return 9 + } + return 0 +} + + +Expected_Token :: struct { + pos: int, + kind: Token_Kind, +} + +Invalid_Repetition :: struct { + pos: int, +} + +Invalid_Token :: struct { + pos: int, + kind: Token_Kind, +} + +Invalid_Unicode :: struct { + pos: int, +} + +Too_Many_Capture_Groups :: struct { + pos: int, +} + +Unexpected_EOF :: struct { + pos: int, +} + +Error :: union { + Expected_Token, + Invalid_Repetition, + Invalid_Token, + Invalid_Unicode, + Too_Many_Capture_Groups, + Unexpected_EOF, +} + + +Parser :: struct { + flags: common.Flags, + t: Tokenizer, + + cur_token: Token, + + groups: int, +} + + +@require_results +advance :: proc(p: ^Parser) -> Error { + p.cur_token = tokenizer.scan(&p.t) + if p.cur_token.kind == .Invalid { + return Invalid_Unicode { pos = 0 } + } + return nil +} + +expect :: proc(p: ^Parser, kind: Token_Kind) -> (err: Error) { + if p.cur_token.kind == kind { + advance(p) or_return + return + } + + return Expected_Token{ + pos = p.t.offset, + kind = kind, + } +} + +null_denotation :: proc(p: ^Parser, token: Token) -> (result: Node, err: Error) { + #partial switch token.kind { + case .Rune: + r: rune + for ru in token.text { + r = ru + break + } + assert(r != 0, "Parsed an empty Rune token.") + + if .Case_Insensitive in p.flags { + lower := unicode.to_lower(r) + upper := unicode.to_upper(r) + if lower != upper { + node := new(Node_Rune_Class) + append(&node.runes, lower) + append(&node.runes, upper) + return node, nil + } + } + + node := new(Node_Rune) + node ^= { r } + return node, nil + + case .Rune_Class: + if len(token.text) == 0 { + return nil, nil + } + + node := new(Node_Rune_Class) + + #no_bounds_check for i := 0; i < len(token.text); /**/ { + r, size := utf8.decode_rune(token.text[i:]) + if i == 0 && r == '^' { + node.negating = true + i += size + continue + } + i += size + + assert(size > 0, "RegEx tokenizer passed an incomplete Rune_Class to the parser.") + + if r == '\\' { + next_r, next_size := utf8.decode_rune(token.text[i:]) + i += next_size + assert(next_size > 0, "RegEx tokenizer passed an incomplete Rune_Class to the parser.") + + // @MetaCharacter + // NOTE: These must be kept in sync with the tokenizer. + switch next_r { + case 'f': append(&node.runes, '\f') + case 'n': append(&node.runes, '\n') + case 'r': append(&node.runes, '\r') + case 't': append(&node.runes, '\t') + + case 'd': + append(&node.ranges, Rune_Class_Range{ '0', '9' }) + case 's': + append(&node.runes, '\t') + append(&node.runes, '\n') + append(&node.runes, '\f') + append(&node.runes, '\r') + append(&node.runes, ' ') + case 'w': + append(&node.ranges, Rune_Class_Range{ '0', '9' }) + append(&node.ranges, Rune_Class_Range{ 'A', 'Z' }) + append(&node.runes, '_') + append(&node.ranges, Rune_Class_Range{ 'a', 'z' }) + case 'D': + append(&node.ranges, Rune_Class_Range{ 0, '0' - 1 }) + append(&node.ranges, Rune_Class_Range{ '9' + 1, max(rune) }) + case 'S': + append(&node.ranges, Rune_Class_Range{ 0, '\t' - 1 }) + // \t and \n are adjacent. + append(&node.runes, '\x0b') // Vertical Tab + append(&node.ranges, Rune_Class_Range{ '\r' + 1, ' ' - 1 }) + append(&node.ranges, Rune_Class_Range{ ' ' + 1, max(rune) }) + case 'W': + append(&node.ranges, Rune_Class_Range{ 0, '0' - 1 }) + append(&node.ranges, Rune_Class_Range{ '9' + 1, 'A' - 1 }) + append(&node.ranges, Rune_Class_Range{ 'Z' + 1, '_' - 1 }) + append(&node.ranges, Rune_Class_Range{ '_' + 1, 'a' - 1 }) + append(&node.ranges, Rune_Class_Range{ 'z' + 1, max(rune) }) + case: + append(&node.runes, next_r) + } + continue + } + + if r == '-' && len(node.runes) > 0 { + next_r, next_size := utf8.decode_rune(token.text[i:]) + if next_size > 0 { + last := pop(&node.runes) + i += next_size + + append(&node.ranges, Rune_Class_Range{ last, next_r }) + continue + } + } + + append(&node.runes, r) + } + + if .Case_Insensitive in p.flags { + // These two loops cannot be in the form of `for x in y` because + // they append to the data that they iterate over. + length := len(node.runes) + #no_bounds_check for i := 0; i < length; i += 1 { + r := node.runes[i] + lower := unicode.to_lower(r) + upper := unicode.to_upper(r) + + if lower != upper { + if lower != r { + append(&node.runes, lower) + } else { + append(&node.runes, upper) + } + } + } + + length = len(node.ranges) + #no_bounds_check for i := 0; i < length; i += 1 { + range := &node.ranges[i] + + min_lower := unicode.to_lower(range.lower) + max_lower := unicode.to_lower(range.upper) + + min_upper := unicode.to_upper(range.lower) + max_upper := unicode.to_upper(range.upper) + + if min_lower != min_upper && max_lower != max_upper { + range.lower = min_lower + range.upper = max_lower + append(&node.ranges, Rune_Class_Range{ min_upper, max_upper }) + } + } + } + + result = node + + case .Wildcard: + node := new(Node_Wildcard) + result = node + + case .Open_Paren: + // Because of the recursive nature of the token parser, we take the + // group number first instead of afterwards, in order to construct + // group matches from the outside in. + p.groups += 1 + if p.groups == common.MAX_CAPTURE_GROUPS { + return nil, Too_Many_Capture_Groups{ pos = token.pos } + } + this_group := p.groups + + node := new(Node_Group) + node.capture = true + node.capture_id = this_group + + node.inner = parse_expression(p, 0) or_return + expect(p, .Close_Paren) or_return + result = node + case .Open_Paren_Non_Capture: + node := new(Node_Group) + node.inner = parse_expression(p, 0) or_return + expect(p, .Close_Paren) or_return + result = node + case .Close_Paren: + node := new(Node_Rune) + node ^= { ')' } + return node, nil + + case .Anchor_Start: + node := new(Node_Anchor) + node.start = true + result = node + case .Anchor_End: + node := new(Node_Anchor) + result = node + case .Word_Boundary: + node := new(Node_Word_Boundary) + result = node + case .Non_Word_Boundary: + node := new(Node_Word_Boundary) + node.non_word = true + result = node + + case .Alternate: + // A unary alternation with a left-side empty path, i.e. `|a`. + right, right_err := parse_expression(p, left_binding_power(.Alternate)) + #partial switch specific in right_err { + case Unexpected_EOF: + // This token is a NOP, i.e. `|`. + break + case nil: + break + case: + return nil, right_err + } + + node := new(Node_Alternation) + node.right = right + result = node + + case .EOF: + return nil, Unexpected_EOF{ pos = token.pos } + + case: + return nil, Invalid_Token{ pos = token.pos, kind = token.kind } + } + + return +} + +left_denotation :: proc(p: ^Parser, token: Token, left: Node) -> (result: Node, err: Error) { + #partial switch token.kind { + case .Alternate: + if p.cur_token.kind == .Close_Paren { + // `(a|)` + // parse_expression will fail, so intervene here. + node := new(Node_Alternation) + node.left = left + return node, nil + } + + right, right_err := parse_expression(p, left_binding_power(.Alternate)) + + #partial switch specific in right_err { + case nil: + break + case Unexpected_EOF: + // EOF is okay in an alternation; it's an edge case in the way of + // expressing an optional such as `a|`. + break + case: + return nil, right_err + } + + node := new(Node_Alternation) + node.left = left + node.right = right + result = node + + case .Concatenate: + right := parse_expression(p, left_binding_power(.Concatenate)) or_return + + // There should be no need to check if right is Node_Concatenation, due + // to how the parsing direction works. + #partial switch specific in left { + case ^Node_Concatenation: + append(&specific.nodes, right) + result = specific + case: + node := new(Node_Concatenation) + append(&node.nodes, left) + append(&node.nodes, right) + result = node + } + + case .Repeat_Zero: + node := new(Node_Repeat_Zero) + node.inner = left + result = node + case .Repeat_Zero_Non_Greedy: + node := new(Node_Repeat_Zero_Non_Greedy) + node.inner = left + result = node + case .Repeat_One: + node := new(Node_Repeat_One) + node.inner = left + result = node + case .Repeat_One_Non_Greedy: + node := new(Node_Repeat_One_Non_Greedy) + node.inner = left + result = node + + case .Repeat_N: + node := new(Node_Repeat_N) + node.inner = left + + comma := strings.index_byte(token.text, ',') + + switch comma { + case -1: // {N} + exact, ok := strconv.parse_u64_of_base(token.text, base = 10) + if !ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + if exact == 0 { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = cast(int)exact + node.upper = cast(int)exact + + case 0: // {,M} + upper, ok := strconv.parse_u64_of_base(token.text[1:], base = 10) + if !ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + if upper == 0 { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = -1 + node.upper = cast(int)upper + + case len(token.text) - 1: // {N,} + lower, ok := strconv.parse_u64_of_base(token.text[:comma], base = 10) + if !ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = cast(int)lower + node.upper = -1 + + case: // {N,M} + lower, lower_ok := strconv.parse_u64_of_base(token.text[:comma], base = 10) + if !lower_ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + upper, upper_ok := strconv.parse_u64_of_base(token.text[comma+1:], base = 10) + if !upper_ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + if lower > upper { + return nil, Invalid_Repetition{ pos = token.pos } + } + if upper == 0 { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = cast(int)lower + node.upper = cast(int)upper + } + + result = node + + case .Optional: + node := new(Node_Optional) + node.inner = left + result = node + case .Optional_Non_Greedy: + node := new(Node_Optional_Non_Greedy) + node.inner = left + result = node + + case .EOF: + return nil, Unexpected_EOF{ pos = token.pos } + + case: + return nil, Invalid_Token{ pos = token.pos, kind = token.kind } + } + + return +} + +parse_expression :: proc(p: ^Parser, rbp: int) -> (result: Node, err: Error) { + token := p.cur_token + + advance(p) or_return + left := null_denotation(p, token) or_return + + token = p.cur_token + for rbp < left_binding_power(token.kind) { + advance(p) or_return + left = left_denotation(p, token, left) or_return + token = p.cur_token + } + + return left, nil +} + +parse :: proc(str: string, flags: common.Flags) -> (result: Node, err: Error) { + if len(str) == 0 { + node := new(Node_Group) + return node, nil + } + + p: Parser + p.flags = flags + + tokenizer.init(&p.t, str, flags) + + p.cur_token = tokenizer.scan(&p.t) + if p.cur_token.kind == .Invalid { + return nil, Invalid_Unicode { pos = 0 } + } + + node := parse_expression(&p, 0) or_return + result = node + + return +} diff --git a/core/text/regex/regex.odin b/core/text/regex/regex.odin new file mode 100644 index 000000000..3dc26b5c6 --- /dev/null +++ b/core/text/regex/regex.odin @@ -0,0 +1,450 @@ +package regex + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:text/regex/common" +import "core:text/regex/compiler" +import "core:text/regex/optimizer" +import "core:text/regex/parser" +import "core:text/regex/virtual_machine" + +Flag :: common.Flag +Flags :: common.Flags +Parser_Error :: parser.Error +Compiler_Error :: compiler.Error + +Creation_Error :: enum { + None, + // A `\` was supplied as the delimiter to `create_by_user`. + Bad_Delimiter, + // A pair of delimiters for `create_by_user` was not found. + Expected_Delimiter, + // An unknown letter was supplied to `create_by_user` after the last delimiter. + Unknown_Flag, +} + +Error :: union #shared_nil { + // An error that can occur in the pattern parsing phase. + // + // Most of these are regular expression syntax errors and are either + // context-dependent as to what they mean or have self-explanatory names. + Parser_Error, + // An error that can occur in the pattern compiling phase. + // + // Of the two that can be returned, they have to do with exceeding the + // limitations of the Virtual Machine. + Compiler_Error, + // An error that occurs only for `create_by_user`. + Creation_Error, +} + +/* +This struct corresponds to a set of string captures from a RegEx match. + +`pos` will contain the start and end positions for each string in `groups`, +such that `str[pos[0][0]:pos[0][1]] == groups[0]`. +*/ +Capture :: struct { + pos: [][2]int, + groups: []string, +} + +/* +A compiled Regular Expression value, to be used with the `match_*` procedures. +*/ +Regular_Expression :: struct { + flags: Flags `fmt:"-"`, + class_data: []virtual_machine.Rune_Class_Data `fmt:"-"`, + program: []virtual_machine.Opcode `fmt:"-"`, +} + + +/* +Create a regular expression from a string pattern and a set of flags. + +*Allocates Using Provided Allocators* + +Inputs: +- pattern: The pattern to compile. +- flags: A `bit_set` of RegEx flags. +- permanent_allocator: The allocator to use for the final regular expression. (default: context.allocator) +- temporary_allocator: The allocator to use for the intermediate compilation stages. (default: context.temp_allocator) + +Returns: +- result: The regular expression. +- err: An error, if one occurred. +*/ +@require_results +create :: proc( + pattern: string, + flags: Flags = {}, + permanent_allocator := context.allocator, + temporary_allocator := context.temp_allocator, +) -> (result: Regular_Expression, err: Error) { + + // For the sake of speed and simplicity, we first run all the intermediate + // processes such as parsing and compilation through the temporary + // allocator. + program: [dynamic]virtual_machine.Opcode = --- + class_data: [dynamic]parser.Rune_Class_Data = --- + { + context.allocator = temporary_allocator + + ast := parser.parse(pattern, flags) or_return + + if .No_Optimization not_in flags { + ast, _ = optimizer.optimize(ast, flags) + } + + program, class_data = compiler.compile(ast, flags) or_return + } + + // When that's successful, re-allocate all at once with the permanent + // allocator so everything can be tightly packed. + context.allocator = permanent_allocator + + result.flags = flags + + if len(class_data) > 0 { + result.class_data = make([]virtual_machine.Rune_Class_Data, len(class_data)) + } + for data, i in class_data { + if len(data.runes) > 0 { + result.class_data[i].runes = make([]rune, len(data.runes)) + copy(result.class_data[i].runes, data.runes[:]) + } + if len(data.ranges) > 0 { + result.class_data[i].ranges = make([]virtual_machine.Rune_Class_Range, len(data.ranges)) + copy(result.class_data[i].ranges, data.ranges[:]) + } + } + + result.program = make([]virtual_machine.Opcode, len(program)) + copy(result.program, program[:]) + + return +} + +/* +Create a regular expression from a delimited string pattern, such as one +provided by users of a program or those found in a configuration file. + +They are in the form of: + + [DELIMITER] [regular expression] [DELIMITER] [flags] + +For example, the following strings are valid: + + /hellope/i + #hellope#i + •hellope•i + つhellopeつi + +The delimiter is determined by the very first rune in the string. +The only restriction is that the delimiter cannot be `\`, as that rune is used +to escape the delimiter if found in the middle of the string. + +All runes after the closing delimiter will be parsed as flags: + +- 'g': Global +- 'm': Multiline +- 'i': Case_Insensitive +- 'x': Ignore_Whitespace +- 'u': Unicode +- 'n': No_Capture +- '-': No_Optimization + + +*Allocates Using Provided Allocators* + +Inputs: +- pattern: The delimited pattern with optional flags to compile. +- str: The string to match against. +- permanent_allocator: The allocator to use for the final regular expression. (default: context.allocator) +- temporary_allocator: The allocator to use for the intermediate compilation stages. (default: context.temp_allocator) + +Returns: +- result: The regular expression. +- err: An error, if one occurred. +*/ +@require_results +create_by_user :: proc( + pattern: string, + permanent_allocator := context.allocator, + temporary_allocator := context.temp_allocator, +) -> (result: Regular_Expression, err: Error) { + + if len(pattern) == 0 { + err = .Expected_Delimiter + return + } + + delimiter: rune + start := -1 + end := -1 + + flags: Flags + + escaping: bool + parse_loop: for r, i in pattern { + if delimiter == 0 { + if r == '\\' { + err = .Bad_Delimiter + return + } + delimiter = r + continue parse_loop + } + + if start == -1 { + start = i + } + + if escaping { + escaping = false + continue parse_loop + } + + switch r { + case '\\': + escaping = true + case delimiter: + end = i + break parse_loop + } + } + + if end == -1 { + err = .Expected_Delimiter + return + } + + // `start` is also the size of the delimiter, which is why it's being added + // to `end` here. + for r in pattern[start + end:] { + switch r { + case 'g': flags += { .Global } + case 'm': flags += { .Multiline } + case 'i': flags += { .Case_Insensitive } + case 'x': flags += { .Ignore_Whitespace } + case 'u': flags += { .Unicode } + case 'n': flags += { .No_Capture } + case '-': flags += { .No_Optimization } + case: + err = .Unknown_Flag + return + } + } + + return create(pattern[start:end], flags, permanent_allocator, temporary_allocator) +} + +/* +Match a regular expression against a string and allocate the results into the +returned `capture` structure. + +The resulting capture strings will be slices to the string `str`, not wholly +copied strings, so they won't need to be individually deleted. + +*Allocates Using Provided Allocators* + +Inputs: +- regex: The regular expression. +- str: The string to match against. +- permanent_allocator: The allocator to use for the capture results. (default: context.allocator) +- temporary_allocator: The allocator to use for the virtual machine. (default: context.temp_allocator) + +Returns: +- capture: The capture groups found in the string. +- success: True if the regex matched the string. +*/ +@require_results +match_and_allocate_capture :: proc( + regex: Regular_Expression, + str: string, + permanent_allocator := context.allocator, + temporary_allocator := context.temp_allocator, +) -> (capture: Capture, success: bool) { + + saved: ^[2 * common.MAX_CAPTURE_GROUPS]int + + { + context.allocator = temporary_allocator + + vm := virtual_machine.create(regex.program, str) + vm.class_data = regex.class_data + + if .Unicode in regex.flags { + saved, success = virtual_machine.run(&vm, true) + } else { + saved, success = virtual_machine.run(&vm, false) + } + } + + if saved != nil { + context.allocator = permanent_allocator + + num_groups := 0 + #no_bounds_check for i := 0; i < len(saved); i += 2 { + a, b := saved[i], saved[i + 1] + if a == -1 || b == -1 { + continue + } + num_groups += 1 + } + + if num_groups > 0 { + capture.groups = make([]string, num_groups) + capture.pos = make([][2]int, num_groups) + n := 0 + + #no_bounds_check for i := 0; i < len(saved); i += 2 { + a, b := saved[i], saved[i + 1] + if a == -1 || b == -1 { + continue + } + + capture.groups[n] = str[a:b] + capture.pos[n] = {a, b} + n += 1 + } + } + } + + return +} + +/* +Match a regular expression against a string and save the capture results into +the provided `capture` structure. + +The resulting capture strings will be slices to the string `str`, not wholly +copied strings, so they won't need to be individually deleted. + +*Allocates Using Provided Allocator* + +Inputs: +- regex: The regular expression. +- str: The string to match against. +- capture: A pointer to a Capture structure with `groups` and `pos` already allocated. +- temporary_allocator: The allocator to use for the virtual machine. (default: context.temp_allocator) + +Returns: +- num_groups: The number of capture groups set into `capture`. +- success: True if the regex matched the string. +*/ +@require_results +match_with_preallocated_capture :: proc( + regex: Regular_Expression, + str: string, + capture: ^Capture, + temporary_allocator := context.temp_allocator, +) -> (num_groups: int, success: bool) { + + assert(capture != nil, "Pre-allocated RegEx capture must not be nil.") + assert(len(capture.groups) >= common.MAX_CAPTURE_GROUPS, + "Pre-allocated RegEx capture `groups` must be at least 10 elements long.") + assert(len(capture.pos) >= common.MAX_CAPTURE_GROUPS, + "Pre-allocated RegEx capture `pos` must be at least 10 elements long.") + + saved: ^[2 * common.MAX_CAPTURE_GROUPS]int + + { + context.allocator = temporary_allocator + + vm := virtual_machine.create(regex.program, str) + vm.class_data = regex.class_data + + if .Unicode in regex.flags { + saved, success = virtual_machine.run(&vm, true) + } else { + saved, success = virtual_machine.run(&vm, false) + } + } + + if saved != nil { + n := 0 + + #no_bounds_check for i := 0; i < len(saved); i += 2 { + a, b := saved[i], saved[i + 1] + if a == -1 || b == -1 { + continue + } + + capture.groups[n] = str[a:b] + capture.pos[n] = {a, b} + n += 1 + } + } + + return +} + +match :: proc { + match_and_allocate_capture, + match_with_preallocated_capture, +} + +/* +Allocate a `Capture` in advance for use with `match`. This can save some time +if you plan on performing several matches at once and only need the results +between matches. + +Inputs: +- allocator: (default: context.allocator) + +Returns: +- result: The `Capture` with the maximum number of groups allocated. +*/ +@require_results +preallocate_capture :: proc(allocator := context.allocator) -> (result: Capture) { + context.allocator = allocator + result.pos = make([][2]int, common.MAX_CAPTURE_GROUPS) + result.groups = make([]string, common.MAX_CAPTURE_GROUPS) + return +} + +/* +Free all data allocated by the `create*` procedures. + +*Frees Using Provided Allocator* + +Inputs: +- regex: A regular expression. +- allocator: (default: context.allocator) +*/ +destroy_regex :: proc(regex: Regular_Expression, allocator := context.allocator) { + context.allocator = allocator + delete(regex.program) + for data in regex.class_data { + delete(data.runes) + delete(data.ranges) + } + delete(regex.class_data) +} + +/* +Free all data allocated by the `match_and_allocate_capture` procedure. + +*Frees Using Provided Allocator* + +Inputs: +- capture: A Capture. +- allocator: (default: context.allocator) +*/ +destroy_capture :: proc(capture: Capture, allocator := context.allocator) { + context.allocator = allocator + delete(capture.groups) + delete(capture.pos) +} + +destroy :: proc { + destroy_regex, + destroy_capture, +} diff --git a/core/text/regex/tokenizer/tokenizer.odin b/core/text/regex/tokenizer/tokenizer.odin new file mode 100644 index 000000000..447fe4329 --- /dev/null +++ b/core/text/regex/tokenizer/tokenizer.odin @@ -0,0 +1,357 @@ +package regex_tokenizer + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:text/regex/common" +import "core:unicode/utf8" + +Token_Kind :: enum { + Invalid, + EOF, + + Rune, + Wildcard, + + Alternate, + + Concatenate, + + Repeat_Zero, + Repeat_Zero_Non_Greedy, + Repeat_One, + Repeat_One_Non_Greedy, + + Repeat_N, + + Optional, + Optional_Non_Greedy, + + Rune_Class, + + Open_Paren, + Open_Paren_Non_Capture, + Close_Paren, + + Anchor_Start, + Anchor_End, + + Word_Boundary, + Non_Word_Boundary, +} + +Token :: struct { + kind: Token_Kind, + text: string, + pos: int, +} + +Tokenizer :: struct { + flags: common.Flags, + src: string, + + ch: rune, + offset: int, + read_offset: int, + + last_token_kind: Token_Kind, + held_token: Token, + error_state: Error, + paren_depth: int, +} + +Error :: enum { + None, + Illegal_Null_Character, + Illegal_Codepoint, + Illegal_Byte_Order_Mark, +} + +init :: proc(t: ^Tokenizer, str: string, flags: common.Flags) { + t.src = str + t.flags = flags + t.error_state = advance_rune(t) +} + +peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte { + if t.read_offset+offset < len(t.src) { + return t.src[t.read_offset+offset] + } + return 0 +} + +advance_rune :: proc(t: ^Tokenizer) -> (err: Error) { + if t.error_state != nil { + return t.error_state + } + + if t.read_offset < len(t.src) { + t.offset = t.read_offset + r, w := rune(t.src[t.read_offset]), 1 + switch { + case r == 0: + err = .Illegal_Null_Character + case r >= utf8.RUNE_SELF: + r, w = utf8.decode_rune(t.src[t.read_offset:]) + if r == utf8.RUNE_ERROR && w == 1 { + err = .Illegal_Codepoint + } else if r == utf8.RUNE_BOM && t.offset > 0 { + err = .Illegal_Byte_Order_Mark + } + } + t.read_offset += w + t.ch = r + } else { + t.offset = len(t.src) + t.ch = -1 + } + + t.error_state = err + + return +} + +@require_results +scan_class :: proc(t: ^Tokenizer) -> (str: string, ok: bool) { + start := t.read_offset + + for { + advance_rune(t) + if t.ch == -1 || t.error_state != nil { + return "", false + } + + if t.ch == '\\' { + advance_rune(t) + continue + } + + if t.ch == ']' { + return t.src[start:t.offset], true + } + } + + unreachable() +} + +@require_results +scan_repeat :: proc(t: ^Tokenizer) -> (str: string, ok: bool) { + start := t.read_offset + + for { + advance_rune(t) + if t.ch == -1 { + return "", false + } + if t.ch == '}' { + return t.src[start:t.offset], true + } + } + + unreachable() +} + +@require_results +scan_non_greedy :: proc(t: ^Tokenizer) -> bool { + if peek_byte(t) == '?' { + advance_rune(t) + return true + } + + return false +} + +scan_comment :: proc(t: ^Tokenizer) { + for { + advance_rune(t) + switch t.ch { + case -1: + return + case '\n': + // UNIX newline. + advance_rune(t) + return + case '\r': + // Mac newline. + advance_rune(t) + if t.ch == '\n' { + // Windows newline. + advance_rune(t) + } + return + } + } +} + +@require_results +scan_non_capture_group :: proc(t: ^Tokenizer) -> bool { + if peek_byte(t) == '?' && peek_byte(t, 1) == ':' { + advance_rune(t) + advance_rune(t) + return true + } + + return false +} + +@require_results +scan :: proc(t: ^Tokenizer) -> (token: Token) { + kind: Token_Kind + lit: string + pos := t.offset + + defer { + t.last_token_kind = token.kind + } + + if t.error_state != nil { + t.error_state = nil + return { .Invalid, "", pos } + } + + if t.held_token != {} { + popped := t.held_token + t.held_token = {} + + return popped + } + + ch_loop: for { + switch t.ch { + case -1: + return { .EOF, "", pos } + + case '\\': + advance_rune(t) + + if t.ch == -1 { + return { .EOF, "", pos } + } + + pos = t.offset + + // @MetaCharacter + // NOTE: These must be kept in sync with the compiler. + DIGIT_CLASS :: "0-9" + SPACE_CLASS :: "\t\n\f\r " + WORD_CLASS :: "0-9A-Z_a-z" + + switch t.ch { + case 'b': kind = .Word_Boundary + case 'B': kind = .Non_Word_Boundary + + case 'f': kind = .Rune; lit = "\f" + case 'n': kind = .Rune; lit = "\n" + case 'r': kind = .Rune; lit = "\r" + case 't': kind = .Rune; lit = "\t" + + case 'd': kind = .Rune_Class; lit = DIGIT_CLASS + case 's': kind = .Rune_Class; lit = SPACE_CLASS + case 'w': kind = .Rune_Class; lit = WORD_CLASS + case 'D': kind = .Rune_Class; lit = "^" + DIGIT_CLASS + case 'S': kind = .Rune_Class; lit = "^" + SPACE_CLASS + case 'W': kind = .Rune_Class; lit = "^" + WORD_CLASS + case: + kind = .Rune + lit = t.src[t.offset:t.read_offset] + } + + case '.': + kind = .Wildcard + + case '|': kind = .Alternate + + case '*': kind = .Repeat_Zero_Non_Greedy if scan_non_greedy(t) else .Repeat_Zero + case '+': kind = .Repeat_One_Non_Greedy if scan_non_greedy(t) else .Repeat_One + case '?': kind = .Optional_Non_Greedy if scan_non_greedy(t) else .Optional + + case '[': + if text, ok := scan_class(t); ok { + kind = .Rune_Class + lit = text + } else { + kind = .EOF + } + + case '{': + if text, ok := scan_repeat(t); ok { + kind = .Repeat_N + lit = text + } else { + kind = .EOF + } + + case '(': + kind = .Open_Paren_Non_Capture if scan_non_capture_group(t) else .Open_Paren + t.paren_depth += 1 + case ')': + kind = .Close_Paren + t.paren_depth -= 1 + + case '^': kind = .Anchor_Start + case '$': + kind = .Anchor_End + + case: + if .Ignore_Whitespace in t.flags { + switch t.ch { + case ' ', '\r', '\n', '\t', '\f': + advance_rune(t) + continue ch_loop + case: + break + } + } + if t.ch == '#' && t.paren_depth == 0 { + scan_comment(t) + continue ch_loop + } + + kind = .Rune + lit = t.src[t.offset:t.read_offset] + } + + break ch_loop + } + + if t.error_state != nil { + t.error_state = nil + return { .Invalid, "", pos } + } + + advance_rune(t) + + // The following set of rules dictate where Concatenate tokens are + // automatically inserted. + #partial switch kind { + case + .Close_Paren, + .Alternate, + .Optional, .Optional_Non_Greedy, + .Repeat_Zero, .Repeat_Zero_Non_Greedy, + .Repeat_One, .Repeat_One_Non_Greedy, + .Repeat_N: + // Never prepend a Concatenate before these tokens. + break + case: + #partial switch t.last_token_kind { + case + .Invalid, + .Open_Paren, .Open_Paren_Non_Capture, + .Alternate: + // Never prepend a Concatenate token when the _last token_ was one + // of these. + break + case: + t.held_token = { kind, lit, pos } + return { .Concatenate, "", pos } + } + } + + return { kind, lit, pos } +} diff --git a/core/text/regex/virtual_machine/doc.odin b/core/text/regex/virtual_machine/doc.odin new file mode 100644 index 000000000..1b0694565 --- /dev/null +++ b/core/text/regex/virtual_machine/doc.odin @@ -0,0 +1,175 @@ +/* +package regex_vm implements a threaded virtual machine for interpreting +regular expressions, based on the designs described by Russ Cox and attributed +to both Ken Thompson and Rob Pike. + +The virtual machine executes all threads in lock step, i.e. the string pointer +does not advance until all threads have finished processing the current rune. +The algorithm does not look backwards. + +Threads merge when splitting or jumping to positions already visited by another +thread, based on the observation that each thread having visited one PC +(Program Counter) state will execute identically to the previous thread. + +Each thread keeps a save state of its capture groups, and thread priority is +used to allow higher precedence operations to complete first with correct save +states, such as greedy versus non-greedy repetition. + +For more information, see: https://swtch.com/~rsc/regexp/regexp2.html + + +**Implementation Details:** + +- Each opcode is 8 bits in size, and most instructions have no operands. + +- All operands larger than `u8` are read in system endian order. + +- Jump and Split instructions operate on absolute positions in `u16` operands. + +- Classes such as `[0-9]` are stored in a RegEx-specific slice of structs which + are then dereferenced by a `u8` index from the `Rune_Class` instructions. + +- Each Byte and Rune opcode have their operands stored inline after the opcode, + sized `u8` and `i32` respectively. + +- A bitmap is used to determine which PC positions are occupied by a thread to + perform merging. The bitmap is cleared with every new frame. + +- The VM supports two modes: ASCII and Unicode, decided by a compile-time + boolean constant argument provided to `run`. The procedure differs only in + string decoding. This was done for the sake of performance. + +- No allocations are ever freed; the VM expects an arena or temporary allocator + to be used in the context preceding it. + + +**Opcode Reference:** + + (0x00) Match + + The terminal opcode which ends a thread. This always comes at the end of + the program. + + (0x01) Match_And_Exit + + A modified version of Match which stops the virtual machine entirely. It is + only compiled for `No_Capture` expressions, as those expressions do not + need to determine which thread may have saved the most appropriate capture + groups. + + (0x02) Byte + + Consumes one byte from the text using its operand, which is also a byte. + + (0x03) Rune + + Consumes one Unicode codepoint from the text using its operand, which is + four bytes long in a system-dependent endian order. + + (0x04) Rune_Class + + Consumes one character (which may be an ASCII byte or Unicode codepoint, + wholly dependent on which mode the virtual machine is running in) from the + text. + + The actual data storing what runes and ranges of runes apply to the class + are stored alongside the program in the Regular_Expression structure and + the operand for this opcode is a single byte which indexes into a + collection of these data structures. + + (0x05) Rune_Class_Negated + + A modified version of Rune_Class that functions the same, save for how it + returns the opposite of what Rune_Class matches. + + (0x06) Wildcard + + Consumes one byte or one Unicode codepoint, depending on the VM mode. + + (0x07) Jump + + Sets the Program Counter of a VM thread to the operand, which is a u16. + This opcode is used to implement Alternation (coming at the end of the left + choice) and Repeat_Zero (to cause the thread to loop backwards). + + (0x08) Split + + Spawns a new thread for the X operand and causes the current thread to jump + to the Y operand. This opcode is used to implement Alternation, all the + Repeat variations, and the Optional nodes. + + Splitting threads is how the virtual machine is able to execute optional + control flow paths, letting it evaluate different possible ways to match + text. + + (0x09) Save + + Saves the current string index to a slot on the thread dictated by the + operand. These values will be used later to reconstruct capture groups. + + (0x0A) Assert_Start + + Asserts that the thread is at the beginning of a string. + + (0x0B) Assert_End + + Asserts that the thread is at the end of a string. + + (0x0C) Assert_Word_Boundary + + Asserts that the thread is on a word boundary, which can be the start or + end of the text. This examines both the current rune and the next rune. + + (0x0D) Assert_Non_Word_Boundary + + A modified version of Assert_Word_Boundary that returns the opposite value. + + (0x0E) Multiline_Open + + This opcode is compiled in only when the `Multiline` flag is present, and + it replaces both `^` and `$` text anchors. + + It asserts that either the current thread is on one of the string + boundaries, or it consumes a `\n` or `\r` character. + + If a `\r` character is consumed, the PC will be advanced to the sibling + `Multiline_Close` opcode to optionally consume a `\n` character on the next + frame. + + (0x0F) Multiline_Close + + This opcode is always present after `Multiline_Open`. + + It handles consuming the second half of a complete newline, if necessary. + For example, Windows newlines are represented by the characters `\r\n`, + whereas UNIX newlines are `\n` and Macintosh newlines are `\r`. + + (0x10) Wait_For_Byte + (0x11) Wait_For_Rune + (0x12) Wait_For_Rune_Class + (0x13) Wait_For_Rune_Class_Negated + + These opcodes are an optimization around restarting threads on failed + matches when the beginning to a pattern is predictable and the Global flag + is set. + + They will cause the VM to wait for the next rune to match before splitting, + as would happen in the un-optimized version. + + (0x14) Match_All_And_Escape + + This opcode is an optimized version of `.*$` or `.+$` that causes the + active thread to immediately work on escaping the program by following all + Jumps out to the end. + + While running through the rest of the program, the thread will trigger on + every Save instruction it passes to store the length of the string. + + This way, any time a program hits one of these `.*$` constructs, the + virtual machine can exit early, vastly improving processing times. + + Be aware, this opcode is not compiled in if the `Multiline` flag is on, as + the meaning of `$` changes with that flag. + +*/ +package regex_vm diff --git a/core/text/regex/virtual_machine/util.odin b/core/text/regex/virtual_machine/util.odin new file mode 100644 index 000000000..fa94a139f --- /dev/null +++ b/core/text/regex/virtual_machine/util.odin @@ -0,0 +1,81 @@ +package regex_vm + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +Opcode_Iterator :: struct { + code: Program, + pc: int, +} + +iterate_opcodes :: proc(iter: ^Opcode_Iterator) -> (opcode: Opcode, pc: int, ok: bool) { + if iter.pc >= len(iter.code) { + return + } + + opcode = iter.code[iter.pc] + pc = iter.pc + ok = true + + switch opcode { + case .Match: iter.pc += size_of(Opcode) + case .Match_And_Exit: iter.pc += size_of(Opcode) + case .Byte: iter.pc += size_of(Opcode) + size_of(u8) + case .Rune: iter.pc += size_of(Opcode) + size_of(rune) + case .Rune_Class: iter.pc += size_of(Opcode) + size_of(u8) + case .Rune_Class_Negated: iter.pc += size_of(Opcode) + size_of(u8) + case .Wildcard: iter.pc += size_of(Opcode) + case .Jump: iter.pc += size_of(Opcode) + size_of(u16) + case .Split: iter.pc += size_of(Opcode) + 2 * size_of(u16) + case .Save: iter.pc += size_of(Opcode) + size_of(u8) + case .Assert_Start: iter.pc += size_of(Opcode) + case .Assert_End: iter.pc += size_of(Opcode) + case .Assert_Word_Boundary: iter.pc += size_of(Opcode) + case .Assert_Non_Word_Boundary: iter.pc += size_of(Opcode) + case .Multiline_Open: iter.pc += size_of(Opcode) + case .Multiline_Close: iter.pc += size_of(Opcode) + case .Wait_For_Byte: iter.pc += size_of(Opcode) + size_of(u8) + case .Wait_For_Rune: iter.pc += size_of(Opcode) + size_of(rune) + case .Wait_For_Rune_Class: iter.pc += size_of(Opcode) + size_of(u8) + case .Wait_For_Rune_Class_Negated: iter.pc += size_of(Opcode) + size_of(u8) + case .Match_All_And_Escape: iter.pc += size_of(Opcode) + case: + panic("Invalid opcode found in RegEx program.") + } + + return +} + +opcode_to_name :: proc(opcode: Opcode) -> (str: string) { + switch opcode { + case .Match: str = "Match" + case .Match_And_Exit: str = "Match_And_Exit" + case .Byte: str = "Byte" + case .Rune: str = "Rune" + case .Rune_Class: str = "Rune_Class" + case .Rune_Class_Negated: str = "Rune_Class_Negated" + case .Wildcard: str = "Wildcard" + case .Jump: str = "Jump" + case .Split: str = "Split" + case .Save: str = "Save" + case .Assert_Start: str = "Assert_Start" + case .Assert_End: str = "Assert_End" + case .Assert_Word_Boundary: str = "Assert_Word_Boundary" + case .Assert_Non_Word_Boundary: str = "Assert_Non_Word_Boundary" + case .Multiline_Open: str = "Multiline_Open" + case .Multiline_Close: str = "Multiline_Close" + case .Wait_For_Byte: str = "Wait_For_Byte" + case .Wait_For_Rune: str = "Wait_For_Rune" + case .Wait_For_Rune_Class: str = "Wait_For_Rune_Class" + case .Wait_For_Rune_Class_Negated: str = "Wait_For_Rune_Class_Negated" + case .Match_All_And_Escape: str = "Match_All_And_Escape" + case: str = "" + } + + return +} diff --git a/core/text/regex/virtual_machine/virtual_machine.odin b/core/text/regex/virtual_machine/virtual_machine.odin new file mode 100644 index 000000000..a4fca6c4d --- /dev/null +++ b/core/text/regex/virtual_machine/virtual_machine.odin @@ -0,0 +1,646 @@ +package regex_vm + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +@require import "core:io" +import "core:slice" +import "core:text/regex/common" +import "core:text/regex/parser" +import "core:unicode/utf8" + +Rune_Class_Range :: parser.Rune_Class_Range + +// NOTE: This structure differs intentionally from the one in `regex/parser`, +// as this data doesn't need to be a dynamic array once it hits the VM. +Rune_Class_Data :: struct { + runes: []rune, + ranges: []Rune_Class_Range, +} + +Opcode :: enum u8 { + // | [ operands ] + Match = 0x00, // | + Match_And_Exit = 0x01, // | + Byte = 0x02, // | u8 + Rune = 0x03, // | i32 + Rune_Class = 0x04, // | u8 + Rune_Class_Negated = 0x05, // | u8 + Wildcard = 0x06, // | + Jump = 0x07, // | u16 + Split = 0x08, // | u16, u16 + Save = 0x09, // | u8 + Assert_Start = 0x0A, // | + Assert_End = 0x0B, // | + Assert_Word_Boundary = 0x0C, // | + Assert_Non_Word_Boundary = 0x0D, // | + Multiline_Open = 0x0E, // | + Multiline_Close = 0x0F, // | + Wait_For_Byte = 0x10, // | u8 + Wait_For_Rune = 0x11, // | i32 + Wait_For_Rune_Class = 0x12, // | u8 + Wait_For_Rune_Class_Negated = 0x13, // | u8 + Match_All_And_Escape = 0x14, // | +} + +Thread :: struct { + pc: int, + saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, +} + +Program :: []Opcode + +Machine :: struct { + // Program state + memory: string, + class_data: []Rune_Class_Data, + code: Program, + + // Thread state + top_thread: int, + threads: [^]Thread, + next_threads: [^]Thread, + + // The busy map is used to merge threads based on their program counters. + busy_map: []u64, + + // Global state + string_pointer: int, + + current_rune: rune, + current_rune_size: int, + next_rune: rune, + next_rune_size: int, +} + + +// @MetaCharacter +// NOTE: This must be kept in sync with the compiler & tokenizer. +is_word_class :: #force_inline proc "contextless" (r: rune) -> bool { + switch r { + case '0'..='9', 'A'..='Z', '_', 'a'..='z': + return true + case: + return false + } +} + +set_busy_map :: #force_inline proc "contextless" (vm: ^Machine, pc: int) -> bool #no_bounds_check { + slot := cast(u64)pc >> 6 + bit: u64 = 1 << (cast(u64)pc & 0x3F) + if vm.busy_map[slot] & bit > 0 { + return false + } + vm.busy_map[slot] |= bit + return true +} + +check_busy_map :: #force_inline proc "contextless" (vm: ^Machine, pc: int) -> bool #no_bounds_check { + slot := cast(u64)pc >> 6 + bit: u64 = 1 << (cast(u64)pc & 0x3F) + return vm.busy_map[slot] & bit > 0 +} + +add_thread :: proc(vm: ^Machine, saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, pc: int) #no_bounds_check { + if check_busy_map(vm, pc) { + return + } + + saved := saved + pc := pc + + resolution_loop: for { + if !set_busy_map(vm, pc) { + return + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "] thinking about ") + io.write_string(common.debug_stream, opcode_to_name(vm.code[pc])) + io.write_rune(common.debug_stream, '\n') + } + + #partial switch vm.code[pc] { + case .Jump: + pc = cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode)]) + continue + + case .Split: + jmp_x := cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode)]) + jmp_y := cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode) + size_of(u16)]) + + add_thread(vm, saved, jmp_x) + pc = jmp_y + continue + + case .Save: + new_saved := new([2 * common.MAX_CAPTURE_GROUPS]int) + new_saved ^= saved^ + saved = new_saved + + index := vm.code[pc + size_of(Opcode)] + sp := vm.string_pointer+vm.current_rune_size + saved[index] = sp + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "] saving state: (slot ") + io.write_int(common.debug_stream, cast(int)index) + io.write_string(common.debug_stream, " = ") + io.write_int(common.debug_stream, sp) + io.write_string(common.debug_stream, ")\n") + } + + pc += size_of(Opcode) + size_of(u8) + continue + + case .Assert_Start: + sp := vm.string_pointer+vm.current_rune_size + if sp == 0 { + pc += size_of(Opcode) + continue + } + case .Assert_End: + sp := vm.string_pointer+vm.current_rune_size + if sp == len(vm.memory) { + pc += size_of(Opcode) + continue + } + case .Multiline_Open: + sp := vm.string_pointer+vm.current_rune_size + if sp == 0 || sp == len(vm.memory) { + if vm.next_rune == '\r' || vm.next_rune == '\n' { + // The VM is currently on a newline at the string boundary, + // so consume the newline next frame. + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + } else { + // Skip the `Multiline_Close` opcode. + pc += 2 * size_of(Opcode) + continue + } + } else { + // Not on a string boundary. + // Try to consume a newline next frame in the other opcode loop. + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + } + case .Assert_Word_Boundary: + sp := vm.string_pointer+vm.current_rune_size + if sp == 0 || sp == len(vm.memory) { + pc += size_of(Opcode) + continue + } else { + last_rune_is_wc := is_word_class(vm.current_rune) + this_rune_is_wc := is_word_class(vm.next_rune) + + if last_rune_is_wc && !this_rune_is_wc || !last_rune_is_wc && this_rune_is_wc { + pc += size_of(Opcode) + continue + } + } + case .Assert_Non_Word_Boundary: + sp := vm.string_pointer+vm.current_rune_size + if sp != 0 && sp != len(vm.memory) { + last_rune_is_wc := is_word_class(vm.current_rune) + this_rune_is_wc := is_word_class(vm.next_rune) + + if last_rune_is_wc && this_rune_is_wc || !last_rune_is_wc && !this_rune_is_wc { + pc += size_of(Opcode) + continue + } + } + + case .Wait_For_Byte: + operand := cast(rune)vm.code[pc + size_of(Opcode)] + if vm.next_rune == operand { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case .Wait_For_Rune: + operand := intrinsics.unaligned_load(cast(^rune)&vm.code[pc + size_of(Opcode)]) + if vm.next_rune == operand { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(rune)) + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class: + operand := cast(u8)vm.code[pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check: { + for r in class_data.runes { + if next_rune == r { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + break check + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + break check + } + } + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class_Negated: + operand := cast(u8)vm.code[pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check_negated: { + for r in class_data.runes { + if next_rune == r { + break check_negated + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + break check_negated + } + } + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + } + + break resolution_loop + } + + return +} + +run :: proc(vm: ^Machine, $UNICODE_MODE: bool) -> (saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, ok: bool) #no_bounds_check { + when UNICODE_MODE { + vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory) + } else { + if len(vm.memory) > 0 { + vm.next_rune = cast(rune)vm.memory[0] + vm.next_rune_size = 1 + } + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "### Adding initial thread.\n") + } + + { + starter_saved := new([2 * common.MAX_CAPTURE_GROUPS]int) + starter_saved ^= -1 + + add_thread(vm, starter_saved, 0) + } + + // `add_thread` adds to `next_threads` by default, but we need to put this + // thread in the current thread buffer. + vm.threads, vm.next_threads = vm.next_threads, vm.threads + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "### VM starting.\n") + defer io.write_string(common.debug_stream, "### VM finished.\n") + } + + for { + slice.zero(vm.busy_map[:]) + + assert(vm.string_pointer <= len(vm.memory), "VM string pointer went out of bounds.") + + current_rune := vm.next_rune + vm.current_rune = current_rune + vm.current_rune_size = vm.next_rune_size + when UNICODE_MODE { + vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory[vm.string_pointer+vm.current_rune_size:]) + } else { + if vm.string_pointer+size_of(u8) < len(vm.memory) { + vm.next_rune = cast(rune)vm.memory[vm.string_pointer+size_of(u8)] + vm.next_rune_size = size_of(u8) + } else { + vm.next_rune = 0 + vm.next_rune_size = 0 + } + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, ">>> Dispatching rune: ") + io.write_encoded_rune(common.debug_stream, current_rune) + io.write_byte(common.debug_stream, '\n') + } + + thread_count := vm.top_thread + vm.top_thread = 0 + thread_loop: for i := 0; i < thread_count; i += 1 { + t := vm.threads[i] + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "] stepping on ") + io.write_string(common.debug_stream, opcode_to_name(vm.code[t.pc])) + io.write_byte(common.debug_stream, '\n') + } + + #partial opcode: switch vm.code[t.pc] { + case .Match: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread matched!\n") + } + saved = t.saved + ok = true + break thread_loop + + case .Match_And_Exit: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread matched! (Exiting)\n") + } + return nil, true + + case .Byte: + operand := cast(rune)vm.code[t.pc + size_of(Opcode)] + if current_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + } + + case .Rune: + operand := intrinsics.unaligned_load(cast(^rune)&vm.code[t.pc + size_of(Opcode)]) + if current_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(rune)) + } + + case .Rune_Class: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + + for r in class_data.runes { + if current_rune == r { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break opcode + } + } + for range in class_data.ranges { + if range.lower <= current_rune && current_rune <= range.upper { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break opcode + } + } + + case .Rune_Class_Negated: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + for r in class_data.runes { + if current_rune == r { + break opcode + } + } + for range in class_data.ranges { + if range.lower <= current_rune && current_rune <= range.upper { + break opcode + } + } + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + + case .Wildcard: + add_thread(vm, t.saved, t.pc + size_of(Opcode)) + + case .Multiline_Open: + if current_rune == '\n' { + // UNIX newline. + add_thread(vm, t.saved, t.pc + 2 * size_of(Opcode)) + } else if current_rune == '\r' { + if vm.next_rune == '\n' { + // Windows newline. (1/2) + add_thread(vm, t.saved, t.pc + size_of(Opcode)) + } else { + // Mac newline. + add_thread(vm, t.saved, t.pc + 2 * size_of(Opcode)) + } + } + case .Multiline_Close: + if current_rune == '\n' { + // Windows newline. (2/2) + add_thread(vm, t.saved, t.pc + size_of(Opcode)) + } + + case .Wait_For_Byte: + operand := cast(rune)vm.code[t.pc + size_of(Opcode)] + if vm.next_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Wait_For_Rune: + operand := intrinsics.unaligned_load(cast(^rune)&vm.code[t.pc + size_of(Opcode)]) + if vm.next_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(rune)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check: { + for r in class_data.runes { + if next_rune == r { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break check + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break check + } + } + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class_Negated: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check_negated: { + for r in class_data.runes { + if next_rune == r { + break check_negated + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + break check_negated + } + } + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Match_All_And_Escape: + t.pc += size_of(Opcode) + // The point of this loop is to walk out of wherever this + // opcode lives to the end of the program, while saving the + // index to the length of the string at each pass on the way. + escape_loop: for { + #partial switch vm.code[t.pc] { + case .Match, .Match_And_Exit: + break escape_loop + + case .Jump: + t.pc = cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[t.pc + size_of(Opcode)]) + + case .Save: + index := vm.code[t.pc + size_of(Opcode)] + t.saved[index] = len(vm.memory) + t.pc += size_of(Opcode) + size_of(u8) + + case .Match_All_And_Escape: + // Layering these is fine. + t.pc += size_of(Opcode) + + // If the loop has to process any opcode not listed above, + // it means someone did something odd like `a(.*$)b`, in + // which case, just fail. Technically, the expression makes + // no sense. + case: + break opcode + } + } + + saved = t.saved + ok = true + return + + case: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Opcode: ") + io.write_int(common.debug_stream, cast(int)vm.code[t.pc]) + io.write_string(common.debug_stream, "\n") + } + panic("Invalid opcode in RegEx thread loop.") + } + } + + vm.threads, vm.next_threads = vm.next_threads, vm.threads + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "<<< Frame ended. (Threads: ") + io.write_int(common.debug_stream, vm.top_thread) + io.write_string(common.debug_stream, ")\n") + } + + if vm.string_pointer == len(vm.memory) || vm.top_thread == 0 { + break + } + + vm.string_pointer += vm.current_rune_size + } + + return +} + +opcode_count :: proc(code: Program) -> (opcodes: int) { + iter := Opcode_Iterator{ code, 0 } + for _ in iterate_opcodes(&iter) { + opcodes += 1 + } + return +} + +create :: proc(code: Program, str: string) -> (vm: Machine) { + assert(len(code) > 0, "RegEx VM has no instructions.") + + vm.memory = str + vm.code = code + + sizing := len(code) >> 6 + (1 if len(code) & 0x3F > 0 else 0) + assert(sizing > 0) + vm.busy_map = make([]u64, sizing) + + max_possible_threads := max(1, opcode_count(vm.code) - 1) + + vm.threads = make([^]Thread, max_possible_threads) + vm.next_threads = make([^]Thread, max_possible_threads) + + return +} diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin index 955ab8d21..63275bbc1 100644 --- a/core/text/table/doc.odin +++ b/core/text/table/doc.odin @@ -1,8 +1,8 @@ /* The package `table` implements plain-text/markdown/HTML/custom rendering of tables. -**Custom rendering example:** - +**Custom rendering.** +Example: package main import "core:io" @@ -24,13 +24,12 @@ The package `table` implements plain-text/markdown/HTML/custom rendering of tabl } } -This outputs: - +Output: A_LONG_ENUM = 54, // A comment about A_LONG_ENUM AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM -**Plain-text rendering example:** - +**Plain-text rendering.** +Example: package main import "core:fmt" @@ -81,8 +80,7 @@ This outputs: table.write_markdown_table(stdout, tbl) } -This outputs: - +Output: +-----------------------------------------------+ | This is a table caption and it is very long | +------------------+-----------------+----------+ @@ -93,16 +91,12 @@ This outputs: | a | bbb | c | +------------------+-----------------+----------+ -and - | AAAAAAAAA | B | C | |:-----------------|:---------------:|---------:| | 123 | foo | | | 000000005 | 6.283185 | | | a | bbb | c | -respectively. - Additionally, if you want to set the alignment and values in-line while constructing a table, you can use `aligned_row_of_values` or @@ -116,8 +110,7 @@ constructing a table, you can use `aligned_row_of_values` or If you only need to build a table once but display it potentially many times, it may be more efficient to cache the results of your write into a string. -Here's an example of how you can do that: - +Example: package main import "core:fmt" @@ -191,8 +184,7 @@ This package makes use of the `grapheme_count` procedure from the implementation for counting graphemes and calculating visual width of a Unicode grapheme cluster in monospace cells. -Here is a full example of how well-supported Unicode is with this package: - +Example: package main import "core:fmt" @@ -237,7 +229,7 @@ Here is a full example of how well-supported Unicode is with this package: scripts(stdout) } -This will print out: +Output: +----------------------------------------------------------------------------------------------------------------------------+ | Tést Suite | @@ -271,8 +263,7 @@ If you'd prefer to change the borders used by the plain-text table printing, there is the `write_decorated_table` procedure that allows you to change the corners and dividers. -Here is a complete example: - +Example: package main import "core:fmt" diff --git a/core/text/table/table.odin b/core/text/table/table.odin index 27c99b1f1..66a7d442b 100644 --- a/core/text/table/table.odin +++ b/core/text/table/table.odin @@ -56,7 +56,7 @@ Decorations :: struct { // Connecting decorations: nw, n, ne, - w, x, e, + w, x, e, sw, s, se: string, // Straight lines: diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index da5e116ff..9bcc42968 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -122,9 +122,10 @@ pool_join :: proc(pool: ^Pool) { for started_count < len(pool.threads) { started_count = 0 for t in pool.threads { - if .Started in t.flags { + flags := intrinsics.atomic_load(&t.flags) + if .Started in flags { started_count += 1 - if .Joined not_in t.flags { + if .Joined not_in flags { join(t) } } @@ -175,10 +176,12 @@ pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool intrinsics.atomic_sub(&pool.num_outstanding, 1) intrinsics.atomic_sub(&pool.num_in_processing, 1) + old_thread_user_index := t.user_index + destroy(t) replacement := create(pool_thread_runner) - replacement.user_index = t.user_index + replacement.user_index = old_thread_user_index replacement.data = data data.task = {} pool.threads[i] = replacement @@ -207,10 +210,12 @@ pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { intrinsics.atomic_sub(&pool.num_outstanding, 1) intrinsics.atomic_sub(&pool.num_in_processing, 1) + old_thread_user_index := t.user_index + destroy(t) replacement := create(pool_thread_runner) - replacement.user_index = t.user_index + replacement.user_index = old_thread_user_index replacement.data = data data.task = {} pool.threads[i] = replacement diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index f56454bfc..ddc47244c 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -2,6 +2,7 @@ // +private package thread +import "base:runtime" import "core:sync" import "core:sys/unix" import "core:time" @@ -55,7 +56,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Unix, we start the OS thread in a running state, and so we manually have it wait on a condition // variable above. We must perform that waiting BEFORE we select the context! context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) } @@ -65,6 +69,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { sync.unlock(&t.mutex) if .Self_Cleanup in sync.atomic_load(&t.flags) { + res := unix.pthread_detach(t.unix_thread) + assert_contextless(res == 0) + t.unix_thread = {} // NOTE(ftphikari): It doesn't matter which context 'free' received, right? context = {} diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 8da75a2d9..50a4e5fbc 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -3,6 +3,7 @@ package thread import "base:intrinsics" +import "base:runtime" import "core:sync" import win32 "core:sys/windows" @@ -39,7 +40,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Windows, the thread is created in a suspended state, and so we can select the context anywhere before the call // to t.procedure(). context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) } diff --git a/core/time/time.odin b/core/time/time.odin index e4ec67be3..98639b36a 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -344,11 +344,33 @@ 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, a duration or a stopwatch's total, including nanoseconds. +*/ +precise_clock :: proc { precise_clock_from_time, precise_clock_from_duration, precise_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)) + hour, min, sec, _ = precise_clock_from_time(t) + return +} + +/* +Obtain the time components from a time, including nanoseconds. +*/ +precise_clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec, nanos: int) { + // Time in nanoseconds since 1-1-1970 00:00 + _sec, _nanos := t._nsec / 1e9, t._nsec % 1e9 + _sec += INTERNAL_TO_ABSOLUTE + nanos = int(_nanos) + sec = int(_sec % SECONDS_PER_DAY) + hour = sec / SECONDS_PER_HOUR + sec -= hour * SECONDS_PER_HOUR + min = sec / SECONDS_PER_MINUTE + sec -= min * SECONDS_PER_MINUTE + return } /* @@ -358,6 +380,13 @@ clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) return clock_from_seconds(u64(d/1e9)) } +/* +Obtain the time components from a duration, including nanoseconds. +*/ +precise_clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec, nanos: int) { + return precise_clock_from_time({_nsec=i64(d)}) +} + /* Obtain the time components from a stopwatch's total. */ @@ -365,11 +394,18 @@ clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: in return clock_from_duration(stopwatch_duration(s)) } +/* +Obtain the time components from a stopwatch's total, including nanoseconds +*/ +precise_clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec, nanos: int) { + return precise_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) +clock_from_seconds :: proc "contextless" (in_sec: u64) -> (hour, min, sec: int) { + sec = int(in_sec % SECONDS_PER_DAY) hour = sec / SECONDS_PER_HOUR sec -= hour * SECONDS_PER_HOUR min = sec / SECONDS_PER_MINUTE @@ -896,18 +932,21 @@ 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) - ok = err == .None - - seconds := delta.days * 86_400 + delta.seconds - nanoseconds := i128(seconds) * 1e9 + i128(delta.nanos) + if err != .None { + return + } + seconds := delta.days * 86_400 + delta.seconds // Can this moment be represented in i64 worth of nanoseconds? // min(Time): 1677-09-21 00:12:44.145224192 +0000 UTC // max(Time): 2262-04-11 23:47:16.854775807 +0000 UTC - if nanoseconds < i128(min(i64)) || nanoseconds > i128(max(i64)) { + if seconds < -9223372036 || (seconds == -9223372036 && delta.nanos < -854775808) { return {}, false } - return Time{_nsec=i64(nanoseconds)}, true + if seconds > 9223372036 || (seconds == 9223372036 && delta.nanos > 854775807) { + return {}, false + } + return Time{_nsec=seconds * 1e9 + delta.nanos}, true } /* @@ -915,6 +954,24 @@ Convert datetime components into time. */ datetime_to_time :: proc{components_to_time, compound_to_time} +/* +Convert time into datetime. +*/ +time_to_datetime :: proc "contextless" (t: Time) -> (dt.DateTime, bool) { + unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} + + datetime, err := dt.add(unix_epoch, dt.Delta{ nanos = t._nsec }) + if err != .None { + return {}, false + } + return datetime, true +} + +/* +Alias for `time_to_datetime`. +*/ +time_to_compound :: time_to_datetime + /* Check if a year is a leap year. */ diff --git a/core/time/time_freestanding.odin b/core/time/time_freestanding.odin deleted file mode 100644 index 7c67cc5e8..000000000 --- a/core/time/time_freestanding.odin +++ /dev/null @@ -1,19 +0,0 @@ -//+private -//+build freestanding -package time - -_IS_SUPPORTED :: false - -_now :: proc "contextless" () -> Time { - return {} -} - -_sleep :: proc "contextless" (d: Duration) { -} - -_tick_now :: proc "contextless" () -> Tick { - return {} -} - -_yield :: proc "contextless" () { -} diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin new file mode 100644 index 000000000..649f601dc --- /dev/null +++ b/core/time/time_linux.odin @@ -0,0 +1,38 @@ +package time + +import "core:sys/linux" + +_IS_SUPPORTED :: true + +_now :: proc "contextless" () -> Time { + time_spec_now, _ := linux.clock_gettime(.REALTIME) + ns := time_spec_now.time_sec * 1e9 + time_spec_now.time_nsec + return Time{_nsec=i64(ns)} +} + +_sleep :: proc "contextless" (d: Duration) { + ds := duration_seconds(d) + seconds := uint(ds) + nanoseconds := uint((ds - f64(seconds)) * 1e9) + + ts := linux.Time_Spec{ + time_sec = seconds, + time_nsec = nanoseconds, + } + + for { + if linux.nanosleep(&ts, &ts) != .EINTR { + break + } + } +} + +_tick_now :: proc "contextless" () -> Tick { + t, _ := linux.clock_gettime(.MONOTONIC_RAW) + return Tick{_nsec = i64(t.time_sec*1e9 + t.time_nsec)} +} + +_yield :: proc "contextless" () { + linux.sched_yield() +} + diff --git a/core/time/time_orca.odin b/core/time/time_orca.odin index d222c8247..b2598fd6e 100644 --- a/core/time/time_orca.odin +++ b/core/time/time_orca.odin @@ -2,23 +2,28 @@ //+build orca package time -_IS_SUPPORTED :: false +import "base:intrinsics" + +import "core:sys/orca" + +_IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { - return {} + CLK_JAN_1970 :: 2208988800 + secs := orca.clock_time(.DATE) + return Time{i64((secs - CLK_JAN_1970) * 1e9)} } _sleep :: proc "contextless" (d: Duration) { + // NOTE: no way to sleep afaict. + if d > 0 { + orca.log_warning("core:time 'sleep' is unimplemented for orca") + } } _tick_now :: proc "contextless" () -> Tick { - // mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { - // q := val / den - // r := val % den - // return q * num + r * num / den - // } - return {} + secs := orca.clock_time(.MONOTONIC) + return Tick{i64(secs * 1e9)} } -_yield :: proc "contextless" () { -} +_yield :: proc "contextless" () {} diff --git a/core/time/time_other.odin b/core/time/time_other.odin new file mode 100644 index 000000000..164d23f25 --- /dev/null +++ b/core/time/time_other.odin @@ -0,0 +1,33 @@ +//+private +//+build !essence +//+build !js +//+build !linux +//+build !openbsd +//+build !freebsd +//+build !netbsd +//+build !darwin +//+build !wasi +//+build !windows +//+build !orca +package time + +_IS_SUPPORTED :: false + +_now :: proc "contextless" () -> Time { + return {} +} + +_sleep :: proc "contextless" (d: Duration) { +} + +_tick_now :: proc "contextless" () -> Tick { + // mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { + // q := val / den + // r := val % den + // return q * num + r * num / den + // } + return {} +} + +_yield :: proc "contextless" () { +} diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index f3e0d6640..0d7a43aba 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,34 +1,50 @@ //+private -//+build linux, darwin, freebsd, openbsd, netbsd, haiku +//+build darwin, freebsd, openbsd, netbsd package time -import "core:sys/unix" +import "core:sys/posix" -_IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. +_IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { - time_spec_now: unix.timespec - unix.clock_gettime(unix.CLOCK_REALTIME, &time_spec_now) - ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec + time_spec_now: posix.timespec + posix.clock_gettime(.REALTIME, &time_spec_now) + ns := i64(time_spec_now.tv_sec) * 1e9 + time_spec_now.tv_nsec return Time{_nsec=ns} } _sleep :: proc "contextless" (d: Duration) { ds := duration_seconds(d) - seconds := u32(ds) + seconds := posix.time_t(ds) nanoseconds := i64((ds - f64(seconds)) * 1e9) - if seconds > 0 { unix.sleep(seconds) } - if nanoseconds > 0 { unix.inline_nanosleep(nanoseconds) } + ts := posix.timespec{ + tv_sec = seconds, + tv_nsec = nanoseconds, + } + + for { + res := posix.nanosleep(&ts, &ts) + if res == .OK || posix.errno() != .EINTR { + break + } + } +} + +when ODIN_OS == .Darwin { + TICK_CLOCK :: posix.Clock(4) // CLOCK_MONOTONIC_RAW +} else { + // It looks like the BSDs don't have a CLOCK_MONOTONIC_RAW equivalent. + TICK_CLOCK :: posix.Clock.MONOTONIC } _tick_now :: proc "contextless" () -> Tick { - t: unix.timespec - unix.clock_gettime(unix.CLOCK_MONOTONIC_RAW, &t) - return Tick{_nsec = t.tv_sec*1e9 + t.tv_nsec} + t: posix.timespec + posix.clock_gettime(TICK_CLOCK, &t) + return Tick{_nsec = i64(t.tv_sec)*1e9 + t.tv_nsec} } _yield :: proc "contextless" () { - unix.sched_yield() + posix.sched_yield() } diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index 3a5554d67..88bebe2e3 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -2,23 +2,40 @@ //+build wasi package time -_IS_SUPPORTED :: false +import "base:intrinsics" + +import "core:sys/wasm/wasi" + +_IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { - return {} + ts, err := wasi.clock_time_get(wasi.CLOCK_REALTIME, 0) + assert_contextless(err == nil) + return Time{_nsec=i64(ts)} } _sleep :: proc "contextless" (d: Duration) { + ev: wasi.event_t + n, err := wasi.poll_oneoff( + &{ + tag = .CLOCK, + clock = { + id = wasi.CLOCK_MONOTONIC, + timeout = wasi.timestamp_t(d), + }, + }, + &ev, + 1, + ) + assert_contextless(err == nil && n == 1 && ev.error == nil && ev.type == .CLOCK) } _tick_now :: proc "contextless" () -> Tick { - // mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { - // q := val / den - // r := val % den - // return q * num + r * num / den - // } - return {} + ts, err := wasi.clock_time_get(wasi.CLOCK_MONOTONIC, 0) + assert_contextless(err == nil) + return Tick{_nsec=i64(ts)} } _yield :: proc "contextless" () { + wasi.sched_yield() } diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 16baa1adf..020ef94e4 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -161,7 +161,7 @@ generate_encoding_entity_table :: proc() { Input: entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. - Output: + Returns: "decoded" - The decoded rune if found by name, or -1 otherwise. "ok" - true if found, false if not. diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index d92a6b8c4..5bd45fda4 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -25,6 +25,7 @@ import rbtree "core:container/rbtree" import topological_sort "core:container/topological_sort" import crypto "core:crypto" +import aead "core:crypto/aead" import aes "core:crypto/aes" import blake2b "core:crypto/blake2b" import blake2s "core:crypto/blake2s" @@ -127,6 +128,7 @@ import testing "core:testing" import edit "core:text/edit" import i18n "core:text/i18n" import match "core:text/match" +import regex "core:text/regex" import scanner "core:text/scanner" import table "core:text/table" @@ -135,6 +137,7 @@ import time "core:time" import datetime "core:time/datetime" import flags "core:flags" +import orca "core:sys/orca" import sysinfo "core:sys/info" import unicode "core:unicode" @@ -164,6 +167,7 @@ _ :: rbtree _ :: topological_sort _ :: crypto _ :: crypto_hash +_ :: aead _ :: aes _ :: blake2b _ :: blake2s @@ -248,12 +252,14 @@ _ :: testing _ :: scanner _ :: i18n _ :: match +_ :: regex _ :: table _ :: edit _ :: thread _ :: time _ :: datetime _ :: flags +_ :: orca _ :: sysinfo _ :: unicode _ :: uuid diff --git a/examples/all/all_posix.odin b/examples/all/all_posix.odin new file mode 100644 index 000000000..819dd6dd3 --- /dev/null +++ b/examples/all/all_posix.odin @@ -0,0 +1,6 @@ +//+build darwin, openbsd, freebsd, netbsd +package all + +import posix "core:sys/posix" + +_ :: posix diff --git a/examples/all/all_vendor_windows.odin b/examples/all/all_vendor_windows.odin index 0c72c886c..50529afc9 100644 --- a/examples/all/all_vendor_windows.odin +++ b/examples/all/all_vendor_windows.odin @@ -1,4 +1,7 @@ package all import wgpu "vendor:wgpu" -_ :: wgpu \ No newline at end of file +import b2 "vendor:box2d" + +_ :: wgpu +_ :: b2 \ No newline at end of file diff --git a/misc/featuregen/README.md b/misc/featuregen/README.md index 22a798cca..82d95a2b6 100644 --- a/misc/featuregen/README.md +++ b/misc/featuregen/README.md @@ -5,7 +5,7 @@ for features regarding microarchitecture and target features of the compiler. It is not pretty! But LLVM has no way to query this information with their C API. -It generates these globals (intended for `src/build_settings.cpp`: +It generates these globals (intended for `src/build_settings_microarch.cpp`: - `target_microarch_list`: an array of strings indexed by the architecture, each string is a comma-seperated list of microarchitectures available on that architecture - `target_features_list`: an array of strings indexed by the architecture, each string is a comma-seperated list of target features available on that architecture @@ -23,6 +23,6 @@ does not impact much at all, the only thing it will do is make LLVM print a mess ## Usage 1. Make sure the table of architectures at the top of the python script is up-to-date (the triple can be any valid triple for the architecture) -1. `./build.sh` +1. `./build_featuregen.sh` 1. `python3 featuregen.py` 1. Copy the output into `src/build_settings.cpp` diff --git a/misc/featuregen/build_featuregen.sh b/misc/featuregen/build_featuregen.sh new file mode 100755 index 000000000..d68f29925 --- /dev/null +++ b/misc/featuregen/build_featuregen.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -ex + +$(llvm-config --bindir)/clang++ $(llvm-config --cxxflags --ldflags --libs) featuregen.cpp -o featuregen diff --git a/misc/featuregen/featuregen.py b/misc/featuregen/featuregen.py index da4cc68f5..ecc47f70c 100644 --- a/misc/featuregen/featuregen.py +++ b/misc/featuregen/featuregen.py @@ -4,12 +4,13 @@ import os import sys archs = [ - ("amd64", "linux_amd64", "x86_64-pc-linux-gnu", [], []), - ("i386", "linux_i386", "i386-pc-linux-gnu", [], []), - ("arm32", "linux_arm32", "arm-linux-gnu", [], []), - ("arm64", "linux_arm64", "aarch64-linux-elf", [], []), - ("wasm32", "js_wasm32", "wasm32-js-js", [], []), - ("wasm64p32", "js_wasm64p32","wasm32-js-js", [], []), + ("amd64", "linux_amd64", "x86_64-pc-linux-gnu", [], []), + ("i386", "linux_i386", "i386-pc-linux-gnu", [], []), + ("arm32", "linux_arm32", "arm-linux-gnu", [], []), + ("arm64", "linux_arm64", "aarch64-linux-elf", [], []), + ("wasm32", "js_wasm32", "wasm32-js-js", [], []), + ("wasm64p32", "js_wasm64p32", "wasm32-js-js", [], []), + ("riscv64", "linux_riscv64", "riscv64-linux-gnu", [], []), ]; SEEKING_CPUS = 0 @@ -78,7 +79,8 @@ print("\t// TargetArch_Invalid:") print('\tstr_lit(""),') for arch, target, triple, cpus, features in archs: print(f"\t// TargetArch_{arch}:") - print(f'\tstr_lit("{','.join(cpus)}"),') + cpus_str = ','.join(cpus) + print(f'\tstr_lit("{cpus_str}"),') print("};") print("") @@ -89,7 +91,8 @@ print("\t// TargetArch_Invalid:") print('\tstr_lit(""),') for arch, target, triple, cpus, features in archs: print(f"\t// TargetArch_{arch}:") - print(f'\tstr_lit("{','.join(features)}"),') + features_str = ','.join(features) + print(f'\tstr_lit("{features_str}"),') print("};") print("") diff --git a/src/big_int.cpp b/src/big_int.cpp index e350687b4..83235483c 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -621,3 +621,7 @@ gb_internal String big_int_to_string(gbAllocator allocator, BigInt const *x, u64 } return make_string(cast(u8 *)buf.data, buf.count); } + +gb_internal int big_int_log2(BigInt const *x) { + return mp_count_bits(x) - 1; +} diff --git a/src/bug_report.cpp b/src/bug_report.cpp index dab8c4391..5f768f57f 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -155,7 +155,7 @@ gb_internal void report_windows_product_type(DWORD ProductType) { #endif gb_internal void odin_cpuid(int leaf, int result[]) { - #if defined(GB_CPU_ARM) + #if defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) return; #elif defined(GB_CPU_X86) @@ -225,6 +225,12 @@ gb_internal void report_cpu_info() { gb_printf("ARM\n"); #endif } + #elif defined(GB_CPU_RISCV) + #if defined(GB_ARCH_64_BIT) + gb_printf("RISCV64\n"); + #else + gb_printf("RISCV32\n"); + #endif #else gb_printf("Unknown\n"); #endif @@ -911,6 +917,8 @@ gb_internal void report_os_info() { {"23E214", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}}, {"23E224", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}}, {"23F79", {23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, + {"23G80", {23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}}, + {"23G93", {23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}}, }; diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3e2d11101..e86224665 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -39,6 +39,7 @@ enum TargetArchKind : u16 { TargetArch_arm64, TargetArch_wasm32, TargetArch_wasm64p32, + TargetArch_riscv64, TargetArch_COUNT, }; @@ -104,6 +105,7 @@ gb_global String target_arch_names[TargetArch_COUNT] = { str_lit("arm64"), str_lit("wasm32"), str_lit("wasm64p32"), + str_lit("riscv64"), }; #include "build_settings_microarch.cpp" @@ -428,6 +430,7 @@ struct BuildContext { bool json_errors; bool has_ansi_terminal_colours; + bool fast_isel; bool ignore_lazy; bool ignore_llvm_build; bool ignore_panic; @@ -518,113 +521,123 @@ gb_internal isize MAX_ERROR_COLLECTOR_COUNT(void) { #define AMD64_MAX_ALIGNMENT 8 #endif +#if LLVM_VERSION_MAJOR >= 18 + #define I386_MAX_ALIGNMENT 16 +#else + #define I386_MAX_ALIGNMENT 4 +#endif + gb_global TargetMetrics target_windows_i386 = { TargetOs_windows, TargetArch_i386, - 4, 4, 4, 8, + 4, 4, I386_MAX_ALIGNMENT, 16, str_lit("i386-pc-windows-msvc"), }; gb_global TargetMetrics target_windows_amd64 = { TargetOs_windows, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-pc-windows-msvc"), }; gb_global TargetMetrics target_linux_i386 = { TargetOs_linux, TargetArch_i386, - 4, 4, 4, 8, + 4, 4, I386_MAX_ALIGNMENT, 16, str_lit("i386-pc-linux-gnu"), - }; gb_global TargetMetrics target_linux_amd64 = { TargetOs_linux, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-pc-linux-gnu"), }; gb_global TargetMetrics target_linux_arm64 = { TargetOs_linux, TargetArch_arm64, - 8, 8, 16, 16, + 8, 8, 16, 32, str_lit("aarch64-linux-elf"), }; - gb_global TargetMetrics target_linux_arm32 = { TargetOs_linux, TargetArch_arm32, - 4, 4, 4, 8, + 4, 4, 8, 16, str_lit("arm-unknown-linux-gnueabihf"), }; +gb_global TargetMetrics target_linux_riscv64 = { + TargetOs_linux, + TargetArch_riscv64, + 8, 8, 16, 32, + str_lit("riscv64-linux-gnu"), +}; gb_global TargetMetrics target_darwin_amd64 = { TargetOs_darwin, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-apple-macosx"), // NOTE: Changes during initialization based on build flags. }; gb_global TargetMetrics target_darwin_arm64 = { TargetOs_darwin, TargetArch_arm64, - 8, 8, 16, 16, + 8, 8, 16, 32, str_lit("arm64-apple-macosx"), // NOTE: Changes during initialization based on build flags. }; gb_global TargetMetrics target_freebsd_i386 = { TargetOs_freebsd, TargetArch_i386, - 4, 4, 4, 8, + 4, 4, I386_MAX_ALIGNMENT, 16, str_lit("i386-unknown-freebsd-elf"), }; gb_global TargetMetrics target_freebsd_amd64 = { TargetOs_freebsd, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-unknown-freebsd-elf"), }; gb_global TargetMetrics target_freebsd_arm64 = { TargetOs_freebsd, TargetArch_arm64, - 8, 8, 16, 16, + 8, 8, 16, 32, str_lit("aarch64-unknown-freebsd-elf"), }; gb_global TargetMetrics target_openbsd_amd64 = { TargetOs_openbsd, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-unknown-openbsd-elf"), }; gb_global TargetMetrics target_netbsd_amd64 = { TargetOs_netbsd, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-unknown-netbsd-elf"), }; gb_global TargetMetrics target_netbsd_arm64 = { TargetOs_netbsd, TargetArch_arm64, - 8, 8, 16, 16, + 8, 8, 16, 32, str_lit("aarch64-unknown-netbsd-elf"), }; gb_global TargetMetrics target_haiku_amd64 = { TargetOs_haiku, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-unknown-haiku"), }; gb_global TargetMetrics target_essence_amd64 = { TargetOs_essence, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-pc-none-elf"), }; @@ -685,7 +698,7 @@ gb_global TargetMetrics target_wasi_wasm64p32 = { gb_global TargetMetrics target_freestanding_amd64_sysv = { TargetOs_freestanding, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-pc-none-gnu"), TargetABI_SysV, }; @@ -693,7 +706,7 @@ gb_global TargetMetrics target_freestanding_amd64_sysv = { gb_global TargetMetrics target_freestanding_amd64_win64 = { TargetOs_freestanding, TargetArch_amd64, - 8, 8, AMD64_MAX_ALIGNMENT, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, str_lit("x86_64-pc-none-msvc"), TargetABI_Win64, }; @@ -701,16 +714,22 @@ gb_global TargetMetrics target_freestanding_amd64_win64 = { gb_global TargetMetrics target_freestanding_arm64 = { TargetOs_freestanding, TargetArch_arm64, - 8, 8, 16, 16, + 8, 8, 16, 32, str_lit("aarch64-none-elf"), }; gb_global TargetMetrics target_freestanding_arm32 = { TargetOs_freestanding, TargetArch_arm32, - 4, 4, 4, 8, + 4, 4, 8, 16, str_lit("arm-unknown-unknown-gnueabihf"), }; +gb_global TargetMetrics target_freestanding_riscv64 = { + TargetOs_freestanding, + TargetArch_riscv64, + 8, 8, 16, 32, + str_lit("riscv64-unknown-gnu"), +}; struct NamedTargetMetrics { @@ -728,6 +747,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("linux_amd64"), &target_linux_amd64 }, { str_lit("linux_arm64"), &target_linux_arm64 }, { str_lit("linux_arm32"), &target_linux_arm32 }, + { str_lit("linux_riscv64"), &target_linux_riscv64 }, { str_lit("windows_i386"), &target_windows_i386 }, { str_lit("windows_amd64"), &target_windows_amd64 }, @@ -756,6 +776,8 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freestanding_arm64"), &target_freestanding_arm64 }, { str_lit("freestanding_arm32"), &target_freestanding_arm32 }, + + { str_lit("freestanding_riscv64"), &target_freestanding_riscv64 }, }; gb_global NamedTargetMetrics *selected_target_metrics; @@ -1504,6 +1526,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) metrics = &target_linux_arm64; + #elif defined(GB_CPU_RISCV) + metrics = &target_linux_riscv64; #else metrics = &target_linux_amd64; #endif @@ -1626,6 +1650,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta // Disallow on wasm bc->use_separate_modules = false; + } if(bc->metrics.arch == TargetArch_riscv64 && bc->cross_compiling) { + bc->link_flags = str_lit("-target riscv64 "); } else { // NOTE: for targets other than darwin, we don't specify a `-target` link flag. // This is because we don't support cross-linking and clang is better at figuring @@ -2020,11 +2046,14 @@ gb_internal bool init_build_paths(String init_filename) { return false; } - 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)); + gbFile output_file_test; + const char* output_file_name = (const char*)output_file.text; + gbFileError output_test_err = gb_file_open_mode(&output_file_test, gbFileMode_Append | gbFileMode_Rw, output_file_name); - if (output_test_err != 0) { + if (output_test_err == 0) { + gb_file_close(&output_file_test); + gb_file_remove(output_file_name); + } else { 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/build_settings_microarch.cpp b/src/build_settings_microarch.cpp index 02b507031..8f64d4026 100644 --- a/src/build_settings_microarch.cpp +++ b/src/build_settings_microarch.cpp @@ -3,17 +3,19 @@ gb_global String target_microarch_list[TargetArch_COUNT] = { // TargetArch_Invalid: str_lit(""), // TargetArch_amd64: - str_lit("alderlake,amdfam10,athlon,athlon-4,athlon-fx,athlon-mp,athlon-tbird,athlon-xp,athlon64,athlon64-sse3,atom,atom_sse4_2,atom_sse4_2_movbe,barcelona,bdver1,bdver2,bdver3,bdver4,bonnell,broadwell,btver1,btver2,c3,c3-2,cannonlake,cascadelake,cooperlake,core-avx-i,core-avx2,core2,core_2_duo_sse4_1,core_2_duo_ssse3,core_2nd_gen_avx,core_3rd_gen_avx,core_4th_gen_avx,core_4th_gen_avx_tsx,core_5th_gen_avx,core_5th_gen_avx_tsx,core_aes_pclmulqdq,core_i7_sse4_2,corei7,corei7-avx,emeraldrapids,generic,geode,goldmont,goldmont-plus,goldmont_plus,grandridge,graniterapids,graniterapids-d,graniterapids_d,haswell,i386,i486,i586,i686,icelake-client,icelake-server,icelake_client,icelake_server,ivybridge,k6,k6-2,k6-3,k8,k8-sse3,knl,knm,lakemont,meteorlake,mic_avx512,nehalem,nocona,opteron,opteron-sse3,penryn,pentium,pentium-m,pentium-mmx,pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium_4,pentium_4_sse3,pentium_ii,pentium_iii,pentium_iii_no_xmm_regs,pentium_m,pentium_mmx,pentium_pro,pentiumpro,prescott,raptorlake,rocketlake,sandybridge,sapphirerapids,sierraforest,silvermont,skx,skylake,skylake-avx512,skylake_avx512,slm,tigerlake,tremont,westmere,winchip-c6,winchip2,x86-64,x86-64-v2,x86-64-v3,x86-64-v4,yonah,znver1,znver2,znver3,znver4"), + str_lit("alderlake,amdfam10,arrowlake,arrowlake-s,arrowlake_s,athlon,athlon-4,athlon-fx,athlon-mp,athlon-tbird,athlon-xp,athlon64,athlon64-sse3,atom,atom_sse4_2,atom_sse4_2_movbe,barcelona,bdver1,bdver2,bdver3,bdver4,bonnell,broadwell,btver1,btver2,c3,c3-2,cannonlake,cascadelake,clearwaterforest,cooperlake,core-avx-i,core-avx2,core2,core_2_duo_sse4_1,core_2_duo_ssse3,core_2nd_gen_avx,core_3rd_gen_avx,core_4th_gen_avx,core_4th_gen_avx_tsx,core_5th_gen_avx,core_5th_gen_avx_tsx,core_aes_pclmulqdq,core_i7_sse4_2,corei7,corei7-avx,emeraldrapids,generic,geode,goldmont,goldmont-plus,goldmont_plus,gracemont,grandridge,graniterapids,graniterapids-d,graniterapids_d,haswell,i386,i486,i586,i686,icelake-client,icelake-server,icelake_client,icelake_server,ivybridge,k6,k6-2,k6-3,k8,k8-sse3,knl,knm,lakemont,lunarlake,meteorlake,mic_avx512,nehalem,nocona,opteron,opteron-sse3,pantherlake,penryn,pentium,pentium-m,pentium-mmx,pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium_4,pentium_4_sse3,pentium_ii,pentium_iii,pentium_iii_no_xmm_regs,pentium_m,pentium_mmx,pentium_pro,pentiumpro,prescott,raptorlake,rocketlake,sandybridge,sapphirerapids,sierraforest,silvermont,skx,skylake,skylake-avx512,skylake_avx512,slm,tigerlake,tremont,westmere,winchip-c6,winchip2,x86-64,x86-64-v2,x86-64-v3,x86-64-v4,yonah,znver1,znver2,znver3,znver4"), // TargetArch_i386: - str_lit("alderlake,amdfam10,athlon,athlon-4,athlon-fx,athlon-mp,athlon-tbird,athlon-xp,athlon64,athlon64-sse3,atom,atom_sse4_2,atom_sse4_2_movbe,barcelona,bdver1,bdver2,bdver3,bdver4,bonnell,broadwell,btver1,btver2,c3,c3-2,cannonlake,cascadelake,cooperlake,core-avx-i,core-avx2,core2,core_2_duo_sse4_1,core_2_duo_ssse3,core_2nd_gen_avx,core_3rd_gen_avx,core_4th_gen_avx,core_4th_gen_avx_tsx,core_5th_gen_avx,core_5th_gen_avx_tsx,core_aes_pclmulqdq,core_i7_sse4_2,corei7,corei7-avx,emeraldrapids,generic,geode,goldmont,goldmont-plus,goldmont_plus,grandridge,graniterapids,graniterapids-d,graniterapids_d,haswell,i386,i486,i586,i686,icelake-client,icelake-server,icelake_client,icelake_server,ivybridge,k6,k6-2,k6-3,k8,k8-sse3,knl,knm,lakemont,meteorlake,mic_avx512,nehalem,nocona,opteron,opteron-sse3,penryn,pentium,pentium-m,pentium-mmx,pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium_4,pentium_4_sse3,pentium_ii,pentium_iii,pentium_iii_no_xmm_regs,pentium_m,pentium_mmx,pentium_pro,pentiumpro,prescott,raptorlake,rocketlake,sandybridge,sapphirerapids,sierraforest,silvermont,skx,skylake,skylake-avx512,skylake_avx512,slm,tigerlake,tremont,westmere,winchip-c6,winchip2,x86-64,x86-64-v2,x86-64-v3,x86-64-v4,yonah,znver1,znver2,znver3,znver4"), + str_lit("alderlake,amdfam10,arrowlake,arrowlake-s,arrowlake_s,athlon,athlon-4,athlon-fx,athlon-mp,athlon-tbird,athlon-xp,athlon64,athlon64-sse3,atom,atom_sse4_2,atom_sse4_2_movbe,barcelona,bdver1,bdver2,bdver3,bdver4,bonnell,broadwell,btver1,btver2,c3,c3-2,cannonlake,cascadelake,clearwaterforest,cooperlake,core-avx-i,core-avx2,core2,core_2_duo_sse4_1,core_2_duo_ssse3,core_2nd_gen_avx,core_3rd_gen_avx,core_4th_gen_avx,core_4th_gen_avx_tsx,core_5th_gen_avx,core_5th_gen_avx_tsx,core_aes_pclmulqdq,core_i7_sse4_2,corei7,corei7-avx,emeraldrapids,generic,geode,goldmont,goldmont-plus,goldmont_plus,gracemont,grandridge,graniterapids,graniterapids-d,graniterapids_d,haswell,i386,i486,i586,i686,icelake-client,icelake-server,icelake_client,icelake_server,ivybridge,k6,k6-2,k6-3,k8,k8-sse3,knl,knm,lakemont,lunarlake,meteorlake,mic_avx512,nehalem,nocona,opteron,opteron-sse3,pantherlake,penryn,pentium,pentium-m,pentium-mmx,pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium_4,pentium_4_sse3,pentium_ii,pentium_iii,pentium_iii_no_xmm_regs,pentium_m,pentium_mmx,pentium_pro,pentiumpro,prescott,raptorlake,rocketlake,sandybridge,sapphirerapids,sierraforest,silvermont,skx,skylake,skylake-avx512,skylake_avx512,slm,tigerlake,tremont,westmere,winchip-c6,winchip2,x86-64,x86-64-v2,x86-64-v3,x86-64-v4,yonah,znver1,znver2,znver3,znver4"), // TargetArch_arm32: - str_lit("arm1020e,arm1020t,arm1022e,arm10e,arm10tdmi,arm1136j-s,arm1136jf-s,arm1156t2-s,arm1156t2f-s,arm1176jz-s,arm1176jzf-s,arm710t,arm720t,arm7tdmi,arm7tdmi-s,arm8,arm810,arm9,arm920,arm920t,arm922t,arm926ej-s,arm940t,arm946e-s,arm966e-s,arm968e-s,arm9e,arm9tdmi,cortex-a12,cortex-a15,cortex-a17,cortex-a32,cortex-a35,cortex-a5,cortex-a53,cortex-a55,cortex-a57,cortex-a7,cortex-a710,cortex-a72,cortex-a73,cortex-a75,cortex-a76,cortex-a76ae,cortex-a77,cortex-a78,cortex-a78c,cortex-a8,cortex-a9,cortex-m0,cortex-m0plus,cortex-m1,cortex-m23,cortex-m3,cortex-m33,cortex-m35p,cortex-m4,cortex-m55,cortex-m7,cortex-m85,cortex-r4,cortex-r4f,cortex-r5,cortex-r52,cortex-r7,cortex-r8,cortex-x1,cortex-x1c,cyclone,ep9312,exynos-m3,exynos-m4,exynos-m5,generic,iwmmxt,krait,kryo,mpcore,mpcorenovfp,neoverse-n1,neoverse-n2,neoverse-v1,sc000,sc300,strongarm,strongarm110,strongarm1100,strongarm1110,swift,xscale"), + str_lit("arm1020e,arm1020t,arm1022e,arm10e,arm10tdmi,arm1136j-s,arm1136jf-s,arm1156t2-s,arm1156t2f-s,arm1176jz-s,arm1176jzf-s,arm710t,arm720t,arm7tdmi,arm7tdmi-s,arm8,arm810,arm9,arm920,arm920t,arm922t,arm926ej-s,arm940t,arm946e-s,arm966e-s,arm968e-s,arm9e,arm9tdmi,cortex-a12,cortex-a15,cortex-a17,cortex-a32,cortex-a35,cortex-a5,cortex-a53,cortex-a55,cortex-a57,cortex-a7,cortex-a710,cortex-a72,cortex-a73,cortex-a75,cortex-a76,cortex-a76ae,cortex-a77,cortex-a78,cortex-a78c,cortex-a8,cortex-a9,cortex-m0,cortex-m0plus,cortex-m1,cortex-m23,cortex-m3,cortex-m33,cortex-m35p,cortex-m4,cortex-m52,cortex-m55,cortex-m7,cortex-m85,cortex-r4,cortex-r4f,cortex-r5,cortex-r52,cortex-r7,cortex-r8,cortex-x1,cortex-x1c,cyclone,ep9312,exynos-m3,exynos-m4,exynos-m5,generic,iwmmxt,krait,kryo,mpcore,mpcorenovfp,neoverse-n1,neoverse-n2,neoverse-v1,sc000,sc300,strongarm,strongarm110,strongarm1100,strongarm1110,swift,xscale"), // TargetArch_arm64: - str_lit("a64fx,ampere1,ampere1a,apple-a10,apple-a11,apple-a12,apple-a13,apple-a14,apple-a15,apple-a16,apple-a7,apple-a8,apple-a9,apple-latest,apple-m1,apple-m2,apple-s4,apple-s5,carmel,cortex-a34,cortex-a35,cortex-a510,cortex-a53,cortex-a55,cortex-a57,cortex-a65,cortex-a65ae,cortex-a710,cortex-a715,cortex-a72,cortex-a73,cortex-a75,cortex-a76,cortex-a76ae,cortex-a77,cortex-a78,cortex-a78c,cortex-r82,cortex-x1,cortex-x1c,cortex-x2,cortex-x3,cyclone,exynos-m3,exynos-m4,exynos-m5,falkor,generic,kryo,neoverse-512tvb,neoverse-e1,neoverse-n1,neoverse-n2,neoverse-v1,neoverse-v2,saphira,thunderx,thunderx2t99,thunderx3t110,thunderxt81,thunderxt83,thunderxt88,tsv110"), + str_lit("a64fx,ampere1,ampere1a,ampere1b,apple-a10,apple-a11,apple-a12,apple-a13,apple-a14,apple-a15,apple-a16,apple-a17,apple-a7,apple-a8,apple-a9,apple-latest,apple-m1,apple-m2,apple-m3,apple-s4,apple-s5,carmel,cortex-a34,cortex-a35,cortex-a510,cortex-a520,cortex-a53,cortex-a55,cortex-a57,cortex-a65,cortex-a65ae,cortex-a710,cortex-a715,cortex-a72,cortex-a720,cortex-a73,cortex-a75,cortex-a76,cortex-a76ae,cortex-a77,cortex-a78,cortex-a78c,cortex-r82,cortex-x1,cortex-x1c,cortex-x2,cortex-x3,cortex-x4,cyclone,exynos-m3,exynos-m4,exynos-m5,falkor,generic,kryo,neoverse-512tvb,neoverse-e1,neoverse-n1,neoverse-n2,neoverse-v1,neoverse-v2,saphira,thunderx,thunderx2t99,thunderx3t110,thunderxt81,thunderxt83,thunderxt88,tsv110"), // TargetArch_wasm32: str_lit("bleeding-edge,generic,mvp"), // TargetArch_wasm64p32: str_lit("bleeding-edge,generic,mvp"), + // TargetArch_riscv64: + str_lit("generic,generic-rv32,generic-rv64,rocket,rocket-rv32,rocket-rv64,sifive-7-series,sifive-e20,sifive-e21,sifive-e24,sifive-e31,sifive-e34,sifive-e76,sifive-p450,sifive-p670,sifive-s21,sifive-s51,sifive-s54,sifive-s76,sifive-u54,sifive-u74,sifive-x280,syntacore-scr1-base,syntacore-scr1-max,veyron-v1,xiangshan-nanhu"), }; // Generated with the featuregen script in `misc/featuregen` @@ -21,17 +23,19 @@ gb_global String target_features_list[TargetArch_COUNT] = { // TargetArch_Invalid: str_lit(""), // TargetArch_amd64: - str_lit("16bit-mode,32bit-mode,3dnow,3dnowa,64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512er,avx512f,avx512fp16,avx512ifma,avx512pf,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,branchfusion,cldemote,clflushopt,clwb,clzero,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,ermsb,f16c,false-deps-getmant,false-deps-lzcnt-tzcnt,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-popcnt,false-deps-range,fast-11bytenop,fast-15bytenop,fast-7bytenop,fast-bextr,fast-gather,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fast-vector-shift-masks,faster-shift-than-shuffle,fma,fma4,fsgsbase,fsrm,fxsr,gfni,harden-sls-ijmp,harden-sls-ret,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lea-sp,lea-uses-ag,lvi-cfi,lvi-load-hardening,lwp,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,mwaitx,no-bypass-delay,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pad-short-functions,pclmul,pconfig,pku,popcnt,prefer-128-bit,prefer-256-bit,prefer-mask-registers,prefer-movmsk-over-vtest,prefer-no-gather,prefer-no-scatter,prefetchi,prefetchwt1,prfchw,ptwrite,raoint,rdpid,rdpru,rdrnd,rdseed,retpoline,retpoline-external-thunk,retpoline-indirect-branches,retpoline-indirect-calls,rtm,sahf,sbb-dep-breaking,serialize,seses,sgx,sha,sha512,shstk,slow-3ops-lea,slow-incdec,slow-lea,slow-pmaddwd,slow-pmulld,slow-shld,slow-two-mem-ops,slow-unaligned-mem-16,slow-unaligned-mem-32,sm3,sm4,soft-float,sse,sse-unaligned-mem,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tagged-globals,tbm,tsxldtrk,tuning-fast-imm-vector-shift,uintr,use-glm-div-sqrt-costs,use-slm-arith-costs,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,widekl,x87,xop,xsave,xsavec,xsaveopt,xsaves"), + str_lit("16bit-mode,32bit-mode,3dnow,3dnowa,64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx10.1-256,avx10.1-512,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512er,avx512f,avx512fp16,avx512ifma,avx512pf,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,branchfusion,ccmp,cf,cldemote,clflushopt,clwb,clzero,cmov,cmpccxadd,crc32,cx16,cx8,egpr,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-lzcnt-tzcnt,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-popcnt,false-deps-range,fast-11bytenop,fast-15bytenop,fast-7bytenop,fast-bextr,fast-gather,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fast-vector-shift-masks,faster-shift-than-shuffle,fma,fma4,fsgsbase,fsrm,fxsr,gfni,harden-sls-ijmp,harden-sls-ret,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lea-sp,lea-uses-ag,lvi-cfi,lvi-load-hardening,lwp,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,mwaitx,ndd,no-bypass-delay,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pad-short-functions,pclmul,pconfig,pku,popcnt,ppx,prefer-128-bit,prefer-256-bit,prefer-mask-registers,prefer-movmsk-over-vtest,prefer-no-gather,prefer-no-scatter,prefetchi,prefetchwt1,prfchw,ptwrite,push2pop2,raoint,rdpid,rdpru,rdrnd,rdseed,retpoline,retpoline-external-thunk,retpoline-indirect-branches,retpoline-indirect-calls,rtm,sahf,sbb-dep-breaking,serialize,seses,sgx,sha,sha512,shstk,slow-3ops-lea,slow-incdec,slow-lea,slow-pmaddwd,slow-pmulld,slow-shld,slow-two-mem-ops,slow-unaligned-mem-16,slow-unaligned-mem-32,sm3,sm4,soft-float,sse,sse-unaligned-mem,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tagged-globals,tbm,tsxldtrk,tuning-fast-imm-vector-shift,uintr,use-glm-div-sqrt-costs,use-slm-arith-costs,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,widekl,x87,xop,xsave,xsavec,xsaveopt,xsaves"), // TargetArch_i386: - str_lit("16bit-mode,32bit-mode,3dnow,3dnowa,64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512er,avx512f,avx512fp16,avx512ifma,avx512pf,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,branchfusion,cldemote,clflushopt,clwb,clzero,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,ermsb,f16c,false-deps-getmant,false-deps-lzcnt-tzcnt,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-popcnt,false-deps-range,fast-11bytenop,fast-15bytenop,fast-7bytenop,fast-bextr,fast-gather,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fast-vector-shift-masks,faster-shift-than-shuffle,fma,fma4,fsgsbase,fsrm,fxsr,gfni,harden-sls-ijmp,harden-sls-ret,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lea-sp,lea-uses-ag,lvi-cfi,lvi-load-hardening,lwp,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,mwaitx,no-bypass-delay,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pad-short-functions,pclmul,pconfig,pku,popcnt,prefer-128-bit,prefer-256-bit,prefer-mask-registers,prefer-movmsk-over-vtest,prefer-no-gather,prefer-no-scatter,prefetchi,prefetchwt1,prfchw,ptwrite,raoint,rdpid,rdpru,rdrnd,rdseed,retpoline,retpoline-external-thunk,retpoline-indirect-branches,retpoline-indirect-calls,rtm,sahf,sbb-dep-breaking,serialize,seses,sgx,sha,sha512,shstk,slow-3ops-lea,slow-incdec,slow-lea,slow-pmaddwd,slow-pmulld,slow-shld,slow-two-mem-ops,slow-unaligned-mem-16,slow-unaligned-mem-32,sm3,sm4,soft-float,sse,sse-unaligned-mem,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tagged-globals,tbm,tsxldtrk,tuning-fast-imm-vector-shift,uintr,use-glm-div-sqrt-costs,use-slm-arith-costs,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,widekl,x87,xop,xsave,xsavec,xsaveopt,xsaves"), + str_lit("16bit-mode,32bit-mode,3dnow,3dnowa,64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx10.1-256,avx10.1-512,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512er,avx512f,avx512fp16,avx512ifma,avx512pf,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,branchfusion,ccmp,cf,cldemote,clflushopt,clwb,clzero,cmov,cmpccxadd,crc32,cx16,cx8,egpr,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-lzcnt-tzcnt,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-popcnt,false-deps-range,fast-11bytenop,fast-15bytenop,fast-7bytenop,fast-bextr,fast-gather,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fast-vector-shift-masks,faster-shift-than-shuffle,fma,fma4,fsgsbase,fsrm,fxsr,gfni,harden-sls-ijmp,harden-sls-ret,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lea-sp,lea-uses-ag,lvi-cfi,lvi-load-hardening,lwp,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,mwaitx,ndd,no-bypass-delay,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pad-short-functions,pclmul,pconfig,pku,popcnt,ppx,prefer-128-bit,prefer-256-bit,prefer-mask-registers,prefer-movmsk-over-vtest,prefer-no-gather,prefer-no-scatter,prefetchi,prefetchwt1,prfchw,ptwrite,push2pop2,raoint,rdpid,rdpru,rdrnd,rdseed,retpoline,retpoline-external-thunk,retpoline-indirect-branches,retpoline-indirect-calls,rtm,sahf,sbb-dep-breaking,serialize,seses,sgx,sha,sha512,shstk,slow-3ops-lea,slow-incdec,slow-lea,slow-pmaddwd,slow-pmulld,slow-shld,slow-two-mem-ops,slow-unaligned-mem-16,slow-unaligned-mem-32,sm3,sm4,soft-float,sse,sse-unaligned-mem,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tagged-globals,tbm,tsxldtrk,tuning-fast-imm-vector-shift,uintr,use-glm-div-sqrt-costs,use-slm-arith-costs,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,widekl,x87,xop,xsave,xsavec,xsaveopt,xsaves"), // TargetArch_arm32: - str_lit("32bit,8msecext,a12,a15,a17,a32,a35,a5,a53,a55,a57,a7,a72,a73,a75,a76,a77,a78c,a8,a9,aapcs-frame-chain,aapcs-frame-chain-leaf,aclass,acquire-release,aes,armv4,armv4t,armv5t,armv5te,armv5tej,armv6,armv6-m,armv6j,armv6k,armv6kz,armv6s-m,armv6t2,armv7-a,armv7-m,armv7-r,armv7e-m,armv7k,armv7s,armv7ve,armv8-a,armv8-m.base,armv8-m.main,armv8-r,armv8.1-a,armv8.1-m.main,armv8.2-a,armv8.3-a,armv8.4-a,armv8.5-a,armv8.6-a,armv8.7-a,armv8.8-a,armv8.9-a,armv9-a,armv9.1-a,armv9.2-a,armv9.3-a,armv9.4-a,atomics-32,avoid-movs-shop,avoid-partial-cpsr,bf16,big-endian-instructions,cde,cdecp0,cdecp1,cdecp2,cdecp3,cdecp4,cdecp5,cdecp6,cdecp7,cheap-predicable-cpsr,clrbhb,cortex-a710,cortex-a78,cortex-x1,cortex-x1c,crc,crypto,d32,db,dfb,disable-postra-scheduler,dont-widen-vmovs,dotprod,dsp,execute-only,expand-fp-mlx,exynos,fix-cmse-cve-2021-35465,fix-cortex-a57-aes-1742098,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp16fml,fp64,fpao,fpregs,fpregs16,fpregs64,fullfp16,fuse-aes,fuse-literals,harden-sls-blr,harden-sls-nocomdat,harden-sls-retbr,hwdiv,hwdiv-arm,i8mm,iwmmxt,iwmmxt2,krait,kryo,lob,long-calls,loop-align,m3,m7,mclass,mp,muxed-units,mve,mve.fp,mve1beat,mve2beat,mve4beat,nacl-trap,neon,neon-fpmovs,neonfp,neoverse-v1,no-branch-predictor,no-bti-at-return-twice,no-movt,no-neg-immediates,noarm,nonpipelined-vfp,pacbti,perfmon,prefer-ishst,prefer-vmovsr,prof-unpr,r4,r5,r52,r7,ras,rclass,read-tp-tpidrprw,read-tp-tpidruro,read-tp-tpidrurw,reserve-r9,ret-addr-stack,sb,sha2,slow-fp-brcc,slow-load-D-subreg,slow-odd-reg,slow-vdup32,slow-vgetlni32,slowfpvfmx,slowfpvmlx,soft-float,splat-vfp-neon,strict-align,swift,thumb-mode,thumb2,trustzone,use-mipipeliner,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.1m.main,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8.8a,v8.9a,v8m,v8m.main,v9.1a,v9.2a,v9.3a,v9.4a,v9a,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vldn-align,vmlx-forwarding,vmlx-hazards,wide-stride-vfp,xscale,zcz"), + str_lit("32bit,8msecext,a12,a15,a17,a32,a35,a5,a53,a55,a57,a7,a72,a73,a75,a76,a77,a78c,a8,a9,aapcs-frame-chain,aapcs-frame-chain-leaf,aclass,acquire-release,aes,armv4,armv4t,armv5t,armv5te,armv5tej,armv6,armv6-m,armv6j,armv6k,armv6kz,armv6s-m,armv6t2,armv7-a,armv7-m,armv7-r,armv7e-m,armv7k,armv7s,armv7ve,armv8-a,armv8-m.base,armv8-m.main,armv8-r,armv8.1-a,armv8.1-m.main,armv8.2-a,armv8.3-a,armv8.4-a,armv8.5-a,armv8.6-a,armv8.7-a,armv8.8-a,armv8.9-a,armv9-a,armv9.1-a,armv9.2-a,armv9.3-a,armv9.4-a,armv9.5-a,atomics-32,avoid-movs-shop,avoid-partial-cpsr,bf16,big-endian-instructions,cde,cdecp0,cdecp1,cdecp2,cdecp3,cdecp4,cdecp5,cdecp6,cdecp7,cheap-predicable-cpsr,clrbhb,cortex-a710,cortex-a78,cortex-x1,cortex-x1c,crc,crypto,d32,db,dfb,disable-postra-scheduler,dont-widen-vmovs,dotprod,dsp,execute-only,expand-fp-mlx,exynos,fix-cmse-cve-2021-35465,fix-cortex-a57-aes-1742098,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp16fml,fp64,fpao,fpregs,fpregs16,fpregs64,fullfp16,fuse-aes,fuse-literals,harden-sls-blr,harden-sls-nocomdat,harden-sls-retbr,hwdiv,hwdiv-arm,i8mm,iwmmxt,iwmmxt2,krait,kryo,lob,long-calls,loop-align,m3,m7,mclass,mp,muxed-units,mve,mve.fp,mve1beat,mve2beat,mve4beat,nacl-trap,neon,neon-fpmovs,neonfp,neoverse-v1,no-branch-predictor,no-bti-at-return-twice,no-movt,no-neg-immediates,noarm,nonpipelined-vfp,pacbti,perfmon,prefer-ishst,prefer-vmovsr,prof-unpr,r4,r5,r52,r7,ras,rclass,read-tp-tpidrprw,read-tp-tpidruro,read-tp-tpidrurw,reserve-r9,ret-addr-stack,sb,sha2,slow-fp-brcc,slow-load-D-subreg,slow-odd-reg,slow-vdup32,slow-vgetlni32,slowfpvfmx,slowfpvmlx,soft-float,splat-vfp-neon,strict-align,swift,thumb-mode,thumb2,trustzone,use-mipipeliner,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.1m.main,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8.8a,v8.9a,v8m,v8m.main,v9.1a,v9.2a,v9.3a,v9.4a,v9.5a,v9a,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vldn-align,vmlx-forwarding,vmlx-hazards,wide-stride-vfp,xscale,zcz"), // TargetArch_arm64: - str_lit("CONTEXTIDREL2,a35,a510,a53,a55,a57,a64fx,a65,a710,a715,a72,a73,a75,a76,a77,a78,a78c,aes,aggressive-fma,all,alternate-sextload-cvt-f32-pattern,altnzcv,am,ampere1,ampere1a,amvs,apple-a10,apple-a11,apple-a12,apple-a13,apple-a14,apple-a15,apple-a16,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,ascend-store-address,b16b16,balance-fp-ops,bf16,brbe,bti,call-saved-x10,call-saved-x11,call-saved-x12,call-saved-x13,call-saved-x14,call-saved-x15,call-saved-x18,call-saved-x8,call-saved-x9,carmel,ccdp,ccidx,ccpp,chk,clrbhb,cmp-bcc-fusion,complxnum,cortex-r82,cortex-x1,cortex-x2,cortex-x3,crc,crypto,cssc,custom-cheap-as-move,d128,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,ete,exynos-cheap-as-move,exynosm3,exynosm4,f32mm,f64mm,falkor,fgt,fix-cortex-a53-835769,flagm,fmv,force-32bit-jump-tables,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-addsub-2reg-const1,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,gcs,harden-sls-blr,harden-sls-nocomdat,harden-sls-retbr,hbc,hcx,i8mm,ite,jsconv,kryo,lor,ls64,lse,lse128,lse2,lsl-fast,mec,mops,mpam,mte,neon,neoverse512tvb,neoversee1,neoversen1,neoversen2,neoversev1,neoversev2,nmi,no-bti-at-return-twice,no-neg-immediates,no-sve-fp-ld1r,no-zcz-fp,nv,outline-atomics,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,prfm-slc-target,rand,ras,rasv2,rcpc,rcpc-immo,rcpc3,rdm,reserve-x1,reserve-x10,reserve-x11,reserve-x12,reserve-x13,reserve-x14,reserve-x15,reserve-x18,reserve-x2,reserve-x20,reserve-x21,reserve-x22,reserve-x23,reserve-x24,reserve-x25,reserve-x26,reserve-x27,reserve-x28,reserve-x3,reserve-x30,reserve-x4,reserve-x5,reserve-x6,reserve-x7,reserve-x9,rme,saphira,sb,sel2,sha2,sha3,slow-misaligned-128store,slow-paired-128,slow-strqro-store,sm4,sme,sme-f16f16,sme-f64f64,sme-i16i64,sme2,sme2p1,spe,spe-eef,specres2,specrestrict,ssbs,strict-align,sve,sve2,sve2-aes,sve2-bitperm,sve2-sha3,sve2-sm4,sve2p1,tagged-globals,the,thunderx,thunderx2t99,thunderx3t110,thunderxt81,thunderxt83,thunderxt88,tlb-rmi,tme,tpidr-el1,tpidr-el2,tpidr-el3,tpidrro-el0,tracev8.4,trbe,tsv110,uaops,use-experimental-zeroing-pseudos,use-postra-scheduler,use-reciprocal-square-root,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8.8a,v8.9a,v8a,v8r,v9.1a,v9.2a,v9.3a,v9.4a,v9a,vh,wfxt,xs,zcm,zcz,zcz-fp-workaround,zcz-gp"), + str_lit("CONTEXTIDREL2,a35,a510,a520,a53,a55,a57,a64fx,a65,a710,a715,a72,a720,a73,a75,a76,a77,a78,a78c,addr-lsl-fast,aes,aggressive-fma,all,alternate-sextload-cvt-f32-pattern,altnzcv,alu-lsl-fast,am,ampere1,ampere1a,ampere1b,amvs,apple-a10,apple-a11,apple-a12,apple-a13,apple-a14,apple-a15,apple-a16,apple-a17,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,ascend-store-address,b16b16,balance-fp-ops,bf16,brbe,bti,call-saved-x10,call-saved-x11,call-saved-x12,call-saved-x13,call-saved-x14,call-saved-x15,call-saved-x18,call-saved-x8,call-saved-x9,carmel,ccdp,ccidx,ccpp,chk,clrbhb,cmp-bcc-fusion,complxnum,cortex-r82,cortex-x1,cortex-x2,cortex-x3,cortex-x4,cpa,crc,crypto,cssc,d128,disable-latency-sched-heuristic,disable-ldp,disable-stp,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,ete,exynos-cheap-as-move,exynosm3,exynosm4,f32mm,f64mm,falkor,faminmax,fgt,fix-cortex-a53-835769,flagm,fmv,force-32bit-jump-tables,fp-armv8,fp16fml,fp8,fp8dot2,fp8dot4,fp8fma,fpmr,fptoint,fullfp16,fuse-address,fuse-addsub-2reg-const1,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,gcs,harden-sls-blr,harden-sls-nocomdat,harden-sls-retbr,hbc,hcx,i8mm,ite,jsconv,kryo,ldp-aligned-only,lor,ls64,lse,lse128,lse2,lut,mec,mops,mpam,mte,neon,neoverse512tvb,neoversee1,neoversen1,neoversen2,neoversev1,neoversev2,nmi,no-bti-at-return-twice,no-neg-immediates,no-sve-fp-ld1r,no-zcz-fp,nv,outline-atomics,pan,pan-rwv,pauth,pauth-lr,perfmon,predictable-select-expensive,predres,prfm-slc-target,rand,ras,rasv2,rcpc,rcpc-immo,rcpc3,rdm,reserve-x1,reserve-x10,reserve-x11,reserve-x12,reserve-x13,reserve-x14,reserve-x15,reserve-x18,reserve-x2,reserve-x20,reserve-x21,reserve-x22,reserve-x23,reserve-x24,reserve-x25,reserve-x26,reserve-x27,reserve-x28,reserve-x3,reserve-x30,reserve-x4,reserve-x5,reserve-x6,reserve-x7,reserve-x9,rme,saphira,sb,sel2,sha2,sha3,slow-misaligned-128store,slow-paired-128,slow-strqro-store,sm4,sme,sme-f16f16,sme-f64f64,sme-f8f16,sme-f8f32,sme-fa64,sme-i16i64,sme-lutv2,sme2,sme2p1,spe,spe-eef,specres2,specrestrict,ssbs,ssve-fp8dot2,ssve-fp8dot4,ssve-fp8fma,store-pair-suppress,stp-aligned-only,strict-align,sve,sve2,sve2-aes,sve2-bitperm,sve2-sha3,sve2-sm4,sve2p1,tagged-globals,the,thunderx,thunderx2t99,thunderx3t110,thunderxt81,thunderxt83,thunderxt88,tlb-rmi,tlbiw,tme,tpidr-el1,tpidr-el2,tpidr-el3,tpidrro-el0,tracev8.4,trbe,tsv110,uaops,use-experimental-zeroing-pseudos,use-postra-scheduler,use-reciprocal-square-root,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8.8a,v8.9a,v8a,v8r,v9.1a,v9.2a,v9.3a,v9.4a,v9.5a,v9a,vh,wfxt,xs,zcm,zcz,zcz-fp-workaround,zcz-gp"), // TargetArch_wasm32: - str_lit("atomics,bulk-memory,exception-handling,extended-const,multivalue,mutable-globals,nontrapping-fptoint,reference-types,relaxed-simd,sign-ext,simd128,tail-call"), + str_lit("atomics,bulk-memory,exception-handling,extended-const,multimemory,multivalue,mutable-globals,nontrapping-fptoint,reference-types,relaxed-simd,sign-ext,simd128,tail-call"), // TargetArch_wasm64p32: - str_lit("atomics,bulk-memory,exception-handling,extended-const,multivalue,mutable-globals,nontrapping-fptoint,reference-types,relaxed-simd,sign-ext,simd128,tail-call"), + str_lit("atomics,bulk-memory,exception-handling,extended-const,multimemory,multivalue,mutable-globals,nontrapping-fptoint,reference-types,relaxed-simd,sign-ext,simd128,tail-call"), + // TargetArch_riscv64: + str_lit("32bit,64bit,a,auipc-addi-fusion,c,conditional-cmv-fusion,d,dlen-factor-2,e,experimental,experimental-zacas,experimental-zcmop,experimental-zfbfmin,experimental-zicfilp,experimental-zicfiss,experimental-zimop,experimental-ztso,experimental-zvfbfmin,experimental-zvfbfwma,f,fast-unaligned-access,forced-atomics,h,i,ld-add-fusion,lui-addi-fusion,m,no-default-unroll,no-optimized-zero-stride-load,no-rvc-hints,relax,reserve-x1,reserve-x10,reserve-x11,reserve-x12,reserve-x13,reserve-x14,reserve-x15,reserve-x16,reserve-x17,reserve-x18,reserve-x19,reserve-x2,reserve-x20,reserve-x21,reserve-x22,reserve-x23,reserve-x24,reserve-x25,reserve-x26,reserve-x27,reserve-x28,reserve-x29,reserve-x3,reserve-x30,reserve-x31,reserve-x4,reserve-x5,reserve-x6,reserve-x7,reserve-x8,reserve-x9,save-restore,seq-cst-trailing-fence,shifted-zextw-fusion,short-forward-branch-opt,sifive7,smaia,smepmp,ssaia,svinval,svnapot,svpbmt,tagged-globals,unaligned-scalar-mem,use-postra-scheduler,v,ventana-veyron,xcvalu,xcvbi,xcvbitmanip,xcvelw,xcvmac,xcvmem,xcvsimd,xsfvcp,xsfvfnrclipxfqf,xsfvfwmaccqqq,xsfvqmaccdod,xsfvqmaccqoq,xtheadba,xtheadbb,xtheadbs,xtheadcmo,xtheadcondmov,xtheadfmemidx,xtheadmac,xtheadmemidx,xtheadmempair,xtheadsync,xtheadvdot,xventanacondops,za128rs,za64rs,zawrs,zba,zbb,zbc,zbkb,zbkc,zbkx,zbs,zca,zcb,zcd,zce,zcf,zcmp,zcmt,zdinx,zexth-fusion,zextw-fusion,zfa,zfh,zfhmin,zfinx,zhinx,zhinxmin,zic64b,zicbom,zicbop,zicboz,ziccamoa,ziccif,zicclsm,ziccrse,zicntr,zicond,zicsr,zifencei,zihintntl,zihintpause,zihpm,zk,zkn,zknd,zkne,zknh,zkr,zks,zksed,zksh,zkt,zmmul,zvbb,zvbc,zve32f,zve32x,zve64d,zve64f,zve64x,zvfh,zvfhmin,zvkb,zvkg,zvkn,zvknc,zvkned,zvkng,zvknha,zvknhb,zvks,zvksc,zvksed,zvksg,zvksh,zvkt,zvl1024b,zvl128b,zvl16384b,zvl2048b,zvl256b,zvl32768b,zvl32b,zvl4096b,zvl512b,zvl64b,zvl65536b,zvl8192b"), }; // Generated with the featuregen script in `misc/featuregen` @@ -39,17 +43,19 @@ gb_global int target_microarch_counts[TargetArch_COUNT] = { // TargetArch_Invalid: 0, // TargetArch_amd64: - 120, + 127, // TargetArch_i386: - 120, + 127, // TargetArch_arm32: - 90, + 91, // TargetArch_arm64: - 63, + 69, // TargetArch_wasm32: 3, // TargetArch_wasm64p32: 3, + // TargetArch_riscv64: + 26, }; // Generated with the featuregen script in `misc/featuregen` @@ -57,6 +63,9 @@ gb_global MicroarchFeatureList microarch_features_list[] = { // TargetArch_amd64: { str_lit("alderlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("amdfam10"), str_lit("3dnow,3dnowa,64bit,64bit-mode,cmov,cx16,cx8,fast-scalar-shift-masks,fxsr,lzcnt,mmx,nopl,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4a,vzeroupper,x87") }, + { str_lit("arrowlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake-s"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake_s"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("athlon"), str_lit("3dnow,3dnowa,64bit-mode,cmov,cx8,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, { str_lit("athlon-4"), str_lit("3dnow,3dnowa,64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, { str_lit("athlon-fx"), str_lit("3dnow,3dnowa,64bit,64bit-mode,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, @@ -81,6 +90,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("c3-2"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, { str_lit("cannonlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vl,bmi,bmi2,clflushopt,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,sha,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("cascadelake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,avx512vnni,bmi,bmi2,clflushopt,clwb,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,faster-shift-than-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("clearwaterforest"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("cooperlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bf16,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,avx512vnni,bmi,bmi2,clflushopt,clwb,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,faster-shift-than-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("core-avx-i"), str_lit("64bit,64bit-mode,avx,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fsgsbase,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, { str_lit("core-avx2"), str_lit("64bit,64bit-mode,allow-light-256-bit,avx,avx2,bmi,bmi2,cmov,crc32,cx16,cx8,ermsb,f16c,false-deps-lzcnt-tzcnt,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, @@ -103,6 +113,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("goldmont"), str_lit("64bit,64bit-mode,aes,clflushopt,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-movbe,fsgsbase,fxsr,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-glm-div-sqrt-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("goldmont-plus"), str_lit("64bit,64bit-mode,aes,clflushopt,cmov,crc32,cx16,cx8,fast-movbe,fsgsbase,fxsr,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-glm-div-sqrt-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("goldmont_plus"), str_lit("64bit,64bit-mode,aes,clflushopt,cmov,crc32,cx16,cx8,fast-movbe,fsgsbase,fxsr,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-glm-div-sqrt-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("gracemont"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("grandridge"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,fast-movbe,fma,fsgsbase,fxsr,gfni,hreset,invpcid,kl,lzcnt,mmx,movbe,movdir64b,movdiri,no-bypass-delay,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,uintr,use-glm-div-sqrt-costs,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("graniterapids"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("graniterapids-d"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, @@ -125,12 +136,14 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("knl"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, { str_lit("knm"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,avx512vpopcntdq,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, { str_lit("lakemont"), str_lit("64bit-mode,cx8,slow-unaligned-mem-16,sse,sse2,vzeroupper") }, + { str_lit("lunarlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("meteorlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("mic_avx512"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, { str_lit("nehalem"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, { str_lit("nocona"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, { str_lit("opteron"), str_lit("3dnow,3dnowa,64bit,64bit-mode,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, { str_lit("opteron-sse3"), str_lit("3dnow,3dnowa,64bit,64bit-mode,cmov,cx16,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("pantherlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("penryn"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,sse4.1,ssse3,vzeroupper,x87") }, { str_lit("pentium"), str_lit("64bit-mode,cx8,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, { str_lit("pentium-m"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, @@ -178,6 +191,9 @@ gb_global MicroarchFeatureList microarch_features_list[] = { // TargetArch_i386: { str_lit("alderlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("amdfam10"), str_lit("32bit-mode,3dnow,3dnowa,64bit,cmov,cx16,cx8,fast-scalar-shift-masks,fxsr,lzcnt,mmx,nopl,popcnt,prfchw,sahf,sbb-dep-breaking,slow-shld,sse,sse2,sse3,sse4a,vzeroupper,x87") }, + { str_lit("arrowlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake-s"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake_s"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("athlon"), str_lit("32bit-mode,3dnow,3dnowa,cmov,cx8,mmx,nopl,slow-shld,slow-unaligned-mem-16,vzeroupper,x87") }, { str_lit("athlon-4"), str_lit("32bit-mode,3dnow,3dnowa,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,vzeroupper,x87") }, { str_lit("athlon-fx"), str_lit("32bit-mode,3dnow,3dnowa,64bit,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, @@ -202,6 +218,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("c3-2"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,slow-unaligned-mem-16,sse,vzeroupper,x87") }, { str_lit("cannonlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vl,bmi,bmi2,clflushopt,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,sha,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("cascadelake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,avx512vnni,bmi,bmi2,clflushopt,clwb,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,faster-shift-than-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("clearwaterforest"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("cooperlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bf16,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,avx512vnni,bmi,bmi2,clflushopt,clwb,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,faster-shift-than-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("core-avx-i"), str_lit("32bit-mode,64bit,avx,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fsgsbase,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, { str_lit("core-avx2"), str_lit("32bit-mode,64bit,allow-light-256-bit,avx,avx2,bmi,bmi2,cmov,crc32,cx16,cx8,ermsb,f16c,false-deps-lzcnt-tzcnt,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, @@ -224,6 +241,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("goldmont"), str_lit("32bit-mode,64bit,aes,clflushopt,cmov,crc32,cx16,cx8,false-deps-popcnt,fast-movbe,fsgsbase,fxsr,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-glm-div-sqrt-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("goldmont-plus"), str_lit("32bit-mode,64bit,aes,clflushopt,cmov,crc32,cx16,cx8,fast-movbe,fsgsbase,fxsr,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-glm-div-sqrt-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("goldmont_plus"), str_lit("32bit-mode,64bit,aes,clflushopt,cmov,crc32,cx16,cx8,fast-movbe,fsgsbase,fxsr,mmx,movbe,no-bypass-delay,nopl,pclmul,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,sha,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,use-glm-div-sqrt-costs,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("gracemont"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("grandridge"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,fast-movbe,fma,fsgsbase,fxsr,gfni,hreset,invpcid,kl,lzcnt,mmx,movbe,movdir64b,movdiri,no-bypass-delay,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,uintr,use-glm-div-sqrt-costs,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("graniterapids"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,amx-bf16,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("graniterapids-d"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, @@ -246,12 +264,14 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("knl"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, { str_lit("knm"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,avx512vpopcntdq,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, { str_lit("lakemont"), str_lit("32bit-mode,cx8,slow-unaligned-mem-16,vzeroupper") }, + { str_lit("lunarlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("meteorlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("mic_avx512"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, { str_lit("nehalem"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, { str_lit("nocona"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, { str_lit("opteron"), str_lit("32bit-mode,3dnow,3dnowa,64bit,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, { str_lit("opteron-sse3"), str_lit("32bit-mode,3dnow,3dnowa,64bit,cmov,cx16,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("pantherlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, { str_lit("penryn"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,sse4.1,ssse3,vzeroupper,x87") }, { str_lit("pentium"), str_lit("32bit-mode,cx8,slow-unaligned-mem-16,vzeroupper,x87") }, { str_lit("pentium-m"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, @@ -325,16 +345,16 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("arm968e-s"), str_lit("armv5te,v4t,v5t,v5te") }, { str_lit("arm9e"), str_lit("armv5te,v4t,v5t,v5te") }, { str_lit("arm9tdmi"), str_lit("armv4t,v4t") }, - { str_lit("cortex-a12"), str_lit("a12,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding") }, - { str_lit("cortex-a15"), str_lit("a15,aclass,armv7-a,avoid-partial-cpsr,d32,db,dont-widen-vmovs,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,muxed-units,neon,perfmon,ret-addr-stack,splat-vfp-neon,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vldn-align") }, - { str_lit("cortex-a17"), str_lit("a17,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding") }, + { str_lit("cortex-a12"), str_lit("a12,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,perfmon,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding") }, + { str_lit("cortex-a15"), str_lit("a15,aclass,armv7-a,avoid-partial-cpsr,d32,db,dont-widen-vmovs,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,muxed-units,perfmon,ret-addr-stack,splat-vfp-neon,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vldn-align") }, + { str_lit("cortex-a17"), str_lit("a17,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,perfmon,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding") }, { str_lit("cortex-a32"), str_lit("aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("cortex-a35"), str_lit("a35,aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, - { str_lit("cortex-a5"), str_lit("a5,aclass,armv7-a,d32,db,dsp,fp16,fp64,fpregs,fpregs64,mp,neon,perfmon,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,vmlx-forwarding") }, + { str_lit("cortex-a5"), str_lit("a5,aclass,armv7-a,d32,db,dsp,fp16,fp64,fpregs,fpregs64,mp,perfmon,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,vmlx-forwarding") }, { str_lit("cortex-a53"), str_lit("a53,aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpao,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("cortex-a55"), str_lit("a55,aclass,acquire-release,aes,armv8.2-a,crc,crypto,d32,db,dotprod,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,ras,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.2a,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("cortex-a57"), str_lit("a57,aclass,acquire-release,aes,armv8-a,avoid-partial-cpsr,cheap-predicable-cpsr,crc,crypto,d32,db,dsp,fix-cortex-a57-aes-1742098,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpao,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, - { str_lit("cortex-a7"), str_lit("a7,aclass,armv7-a,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding,vmlx-hazards") }, + { str_lit("cortex-a7"), str_lit("a7,aclass,armv7-a,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,perfmon,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding,vmlx-hazards") }, { str_lit("cortex-a710"), str_lit("aclass,acquire-release,armv9-a,bf16,cortex-a710,crc,d32,db,dotprod,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp16fml,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,hwdiv-arm,i8mm,mp,neon,perfmon,ras,sb,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8m,v9a,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("cortex-a72"), str_lit("a72,aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fix-cortex-a57-aes-1742098,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("cortex-a73"), str_lit("a73,aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, @@ -344,8 +364,8 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("cortex-a77"), str_lit("a77,aclass,acquire-release,aes,armv8.2-a,crc,crypto,d32,db,dotprod,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,hwdiv-arm,mp,neon,perfmon,ras,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.2a,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("cortex-a78"), str_lit("aclass,acquire-release,aes,armv8.2-a,cortex-a78,crc,crypto,d32,db,dotprod,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,hwdiv-arm,mp,neon,perfmon,ras,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.2a,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("cortex-a78c"), str_lit("a78c,aclass,acquire-release,aes,armv8.2-a,crc,crypto,d32,db,dotprod,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,hwdiv-arm,mp,neon,perfmon,ras,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.2a,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, - { str_lit("cortex-a8"), str_lit("a8,aclass,armv7-a,d32,db,dsp,fp64,fpregs,fpregs64,neon,nonpipelined-vfp,perfmon,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vmlx-forwarding,vmlx-hazards") }, - { str_lit("cortex-a9"), str_lit("a9,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,expand-fp-mlx,fp16,fp64,fpregs,fpregs64,mp,muxed-units,neon,neon-fpmovs,perfmon,prefer-vmovsr,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vldn-align,vmlx-forwarding,vmlx-hazards") }, + { str_lit("cortex-a8"), str_lit("a8,aclass,armv7-a,d32,db,dsp,fp64,fpregs,fpregs64,nonpipelined-vfp,perfmon,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vmlx-forwarding,vmlx-hazards") }, + { str_lit("cortex-a9"), str_lit("a9,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,expand-fp-mlx,fp16,fp64,fpregs,fpregs64,mp,muxed-units,neon-fpmovs,perfmon,prefer-vmovsr,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vldn-align,vmlx-forwarding,vmlx-hazards") }, { str_lit("cortex-m0"), str_lit("armv6-m,db,mclass,no-branch-predictor,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m") }, { str_lit("cortex-m0plus"), str_lit("armv6-m,db,mclass,no-branch-predictor,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m") }, { str_lit("cortex-m1"), str_lit("armv6-m,db,mclass,no-branch-predictor,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m") }, @@ -354,6 +374,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("cortex-m33"), str_lit("8msecext,acquire-release,armv8-m.main,db,dsp,fix-cmse-cve-2021-35465,fp-armv8d16sp,fp16,fpregs,hwdiv,loop-align,mclass,no-branch-predictor,noarm,slowfpvfmx,slowfpvmlx,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,v8m.main,vfp2sp,vfp3d16sp,vfp4d16sp") }, { str_lit("cortex-m35p"), str_lit("8msecext,acquire-release,armv8-m.main,db,dsp,fix-cmse-cve-2021-35465,fp-armv8d16sp,fp16,fpregs,hwdiv,loop-align,mclass,no-branch-predictor,noarm,slowfpvfmx,slowfpvmlx,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,v8m.main,vfp2sp,vfp3d16sp,vfp4d16sp") }, { str_lit("cortex-m4"), str_lit("armv7e-m,db,dsp,fp16,fpregs,hwdiv,loop-align,mclass,no-branch-predictor,noarm,slowfpvfmx,slowfpvmlx,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2sp,vfp3d16sp,vfp4d16sp") }, + { str_lit("cortex-m52"), str_lit("8msecext,acquire-release,armv8.1-m.main,db,dsp,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,lob,loop-align,mclass,mve,mve.fp,mve1beat,no-branch-predictor,noarm,pacbti,ras,slowfpvmlx,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8.1m.main,v8m,v8m.main,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, { str_lit("cortex-m55"), str_lit("8msecext,acquire-release,armv8.1-m.main,db,dsp,fix-cmse-cve-2021-35465,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,lob,loop-align,mclass,mve,mve.fp,no-branch-predictor,noarm,ras,slowfpvmlx,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8.1m.main,v8m,v8m.main,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, { str_lit("cortex-m7"), str_lit("armv7e-m,db,dsp,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs64,hwdiv,m7,mclass,noarm,thumb-mode,thumb2,use-mipipeliner,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, { str_lit("cortex-m85"), str_lit("8msecext,acquire-release,armv8.1-m.main,db,dsp,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,lob,mclass,mve,mve.fp,noarm,pacbti,ras,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8.1m.main,v8m,v8m.main,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, @@ -372,7 +393,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("exynos-m5"), str_lit("aclass,acquire-release,aes,armv8.2-a,crc,crypto,d32,db,dont-widen-vmovs,dotprod,dsp,expand-fp-mlx,exynos,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,fuse-aes,fuse-literals,hwdiv,hwdiv-arm,mp,neon,perfmon,prof-unpr,ras,ret-addr-stack,sha2,slow-fp-brcc,slow-vdup32,slow-vgetlni32,slowfpvfmx,slowfpvmlx,splat-vfp-neon,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.2a,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,wide-stride-vfp,zcz") }, { str_lit("generic"), str_lit("") }, { str_lit("iwmmxt"), str_lit("armv5te,v4t,v5t,v5te") }, - { str_lit("krait"), str_lit("aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,krait,muxed-units,neon,perfmon,ret-addr-stack,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,vldn-align,vmlx-forwarding") }, + { str_lit("krait"), str_lit("aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,krait,muxed-units,perfmon,ret-addr-stack,thumb2,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,vldn-align,vmlx-forwarding") }, { str_lit("kryo"), str_lit("aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,kryo,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, { str_lit("mpcore"), str_lit("armv6k,fp64,fpregs,fpregs64,slowfpvmlx,v4t,v5t,v5te,v6,v6k,vfp2,vfp2sp") }, { str_lit("mpcorenovfp"), str_lit("armv6k,v4t,v5t,v5te,v6,v6k") }, @@ -385,12 +406,13 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("strongarm110"), str_lit("armv4") }, { str_lit("strongarm1100"), str_lit("armv4") }, { str_lit("strongarm1110"), str_lit("armv4") }, - { str_lit("swift"), str_lit("aclass,armv7-a,avoid-movs-shop,avoid-partial-cpsr,d32,db,disable-postra-scheduler,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,neonfp,perfmon,prefer-ishst,prof-unpr,ret-addr-stack,slow-load-D-subreg,slow-odd-reg,slow-vdup32,slow-vgetlni32,slowfpvfmx,slowfpvmlx,swift,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,vmlx-hazards,wide-stride-vfp") }, + { str_lit("swift"), str_lit("aclass,armv7-a,avoid-movs-shop,avoid-partial-cpsr,d32,db,disable-postra-scheduler,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neonfp,perfmon,prefer-ishst,prof-unpr,ret-addr-stack,slow-load-D-subreg,slow-odd-reg,slow-vdup32,slow-vgetlni32,slowfpvfmx,slowfpvmlx,swift,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,vmlx-hazards,wide-stride-vfp") }, { str_lit("xscale"), str_lit("armv5te,v4t,v5t,v5te") }, // TargetArch_arm64: { str_lit("a64fx"), str_lit("CONTEXTIDREL2,a64fx,aggressive-fma,arith-bcc-fusion,ccpp,complxnum,crc,el2vmsa,el3,fp-armv8,fullfp16,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rdm,sha2,store-pair-suppress,sve,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, { str_lit("ampere1"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,aggressive-fma,altnzcv,alu-lsl-fast,am,ampere1,amvs,arith-bcc-fusion,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fptoint,fuse-address,fuse-aes,fuse-literals,i8mm,jsconv,ldp-aligned-only,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,rand,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,stp-aligned-only,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh") }, { str_lit("ampere1a"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,aggressive-fma,altnzcv,alu-lsl-fast,am,ampere1a,amvs,arith-bcc-fusion,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fptoint,fuse-address,fuse-aes,fuse-literals,i8mm,jsconv,ldp-aligned-only,lor,lse,lse2,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predres,rand,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,sm4,specrestrict,ssbs,store-pair-suppress,stp-aligned-only,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh") }, + { str_lit("ampere1b"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,aggressive-fma,altnzcv,alu-lsl-fast,am,ampere1b,amvs,arith-bcc-fusion,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,cssc,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,fgt,flagm,fp-armv8,fptoint,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-literals,hcx,i8mm,jsconv,ldp-aligned-only,lor,lse,lse2,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,rand,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,sm4,specrestrict,ssbs,store-pair-suppress,stp-aligned-only,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,vh,wfxt,xs") }, { str_lit("apple-a10"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a10,arith-bcc-fusion,arith-cbz-fusion,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,lor,neon,pan,perfmon,rdm,sha2,store-pair-suppress,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-a11"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a11,arith-bcc-fusion,arith-cbz-fusion,ccpp,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,lor,lse,neon,pan,pan-rwv,perfmon,ras,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-a12"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a12,arith-bcc-fusion,arith-cbz-fusion,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,jsconv,lor,lse,neon,pan,pan-rwv,pauth,perfmon,ras,rcpc,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8.3a,v8a,vh,zcm,zcz,zcz-gp") }, @@ -398,18 +420,21 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("apple-a14"), str_lit("CONTEXTIDREL2,aes,aggressive-fma,alternate-sextload-cvt-f32-pattern,altnzcv,am,apple-a14,arith-bcc-fusion,arith-cbz-fusion,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,el2vmsa,el3,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-a15"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a15,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-a16"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a16,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,hcx,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-a17"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a17,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,hcx,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-a7"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, { str_lit("apple-a8"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, { str_lit("apple-a9"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, { str_lit("apple-latest"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a16,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,hcx,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-m1"), str_lit("CONTEXTIDREL2,aes,aggressive-fma,alternate-sextload-cvt-f32-pattern,altnzcv,am,apple-a14,arith-bcc-fusion,arith-cbz-fusion,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,el2vmsa,el3,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-m2"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a15,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-m3"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a16,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,hcx,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-s4"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a12,arith-bcc-fusion,arith-cbz-fusion,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,jsconv,lor,lse,neon,pan,pan-rwv,pauth,perfmon,ras,rcpc,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8.3a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("apple-s5"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a12,arith-bcc-fusion,arith-cbz-fusion,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,jsconv,lor,lse,neon,pan,pan-rwv,pauth,perfmon,ras,rcpc,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8.3a,v8a,vh,zcm,zcz,zcz-gp") }, { str_lit("carmel"), str_lit("CONTEXTIDREL2,aes,carmel,ccpp,crc,crypto,el2vmsa,el3,fp-armv8,fullfp16,lor,lse,neon,pan,pan-rwv,ras,rdm,sha2,uaops,v8.1a,v8.2a,v8a,vh") }, { str_lit("cortex-a34"), str_lit("a35,aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,sha2,v8a") }, { str_lit("cortex-a35"), str_lit("a35,aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,sha2,v8a") }, { str_lit("cortex-a510"), str_lit("CONTEXTIDREL2,a510,altnzcv,am,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,dit,dotprod,el2vmsa,el3,ete,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8a,v9a,vh") }, + { str_lit("cortex-a520"), str_lit("CONTEXTIDREL2,a520,altnzcv,am,amvs,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,ete,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,hcx,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,v9.1a,v9.2a,v9a,vh,wfxt,xs") }, { str_lit("cortex-a53"), str_lit("a53,aes,balance-fp-ops,crc,crypto,el2vmsa,el3,fp-armv8,fuse-adrp-add,fuse-aes,neon,perfmon,sha2,use-postra-scheduler,v8a") }, { str_lit("cortex-a55"), str_lit("CONTEXTIDREL2,a55,aes,ccpp,crc,crypto,dotprod,el2vmsa,el3,fp-armv8,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,lor,lse,neon,pan,pan-rwv,perfmon,ras,rcpc,rdm,sha2,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, { str_lit("cortex-a57"), str_lit("a57,aes,balance-fp-ops,crc,crypto,el2vmsa,el3,enable-select-opt,fp-armv8,fuse-adrp-add,fuse-aes,fuse-literals,neon,perfmon,predictable-select-expensive,sha2,use-postra-scheduler,v8a") }, @@ -418,6 +443,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("cortex-a710"), str_lit("CONTEXTIDREL2,a710,addr-lsl-fast,altnzcv,alu-lsl-fast,am,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,el2vmsa,el3,enable-select-opt,ete,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8a,v9a,vh") }, { str_lit("cortex-a715"), str_lit("CONTEXTIDREL2,a715,addr-lsl-fast,altnzcv,alu-lsl-fast,am,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,el2vmsa,el3,enable-select-opt,ete,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,spe,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8a,v9a,vh") }, { str_lit("cortex-a72"), str_lit("a72,aes,crc,crypto,el2vmsa,el3,enable-select-opt,fp-armv8,fuse-adrp-add,fuse-aes,fuse-literals,neon,perfmon,predictable-select-expensive,sha2,v8a") }, + { str_lit("cortex-a720"), str_lit("CONTEXTIDREL2,a720,addr-lsl-fast,altnzcv,alu-lsl-fast,am,amvs,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,ete,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,hcx,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,spe,spe-eef,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,v9.1a,v9.2a,v9a,vh,wfxt,xs") }, { str_lit("cortex-a73"), str_lit("a73,aes,crc,crypto,el2vmsa,el3,enable-select-opt,fp-armv8,fuse-adrp-add,fuse-aes,neon,perfmon,predictable-select-expensive,sha2,v8a") }, { str_lit("cortex-a75"), str_lit("CONTEXTIDREL2,a75,aes,ccpp,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,fp-armv8,fullfp16,fuse-adrp-add,fuse-aes,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,uaops,v8.1a,v8.2a,v8a,vh") }, { str_lit("cortex-a76"), str_lit("CONTEXTIDREL2,a76,addr-lsl-fast,aes,alu-lsl-fast,ccpp,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,fp-armv8,fullfp16,fuse-adrp-add,fuse-aes,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,ssbs,uaops,v8.1a,v8.2a,v8a,vh") }, @@ -430,6 +456,7 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("cortex-x1c"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,alu-lsl-fast,ccpp,cmp-bcc-fusion,cortex-x1,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,flagm,fp-armv8,fullfp16,fuse-adrp-add,fuse-aes,lor,lse,lse2,neon,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,ras,rcpc,rcpc-immo,rdm,sha2,spe,ssbs,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, { str_lit("cortex-x2"), str_lit("CONTEXTIDREL2,addr-lsl-fast,altnzcv,alu-lsl-fast,am,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,cortex-x2,crc,dit,dotprod,el2vmsa,el3,enable-select-opt,ete,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8a,v9a,vh") }, { str_lit("cortex-x3"), str_lit("CONTEXTIDREL2,addr-lsl-fast,altnzcv,alu-lsl-fast,am,bf16,bti,ccdp,ccidx,ccpp,complxnum,cortex-x3,crc,dit,dotprod,el2vmsa,el3,enable-select-opt,ete,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,spe,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8a,v9a,vh") }, + { str_lit("cortex-x4"), str_lit("CONTEXTIDREL2,addr-lsl-fast,altnzcv,alu-lsl-fast,am,amvs,bf16,bti,ccdp,ccidx,ccpp,complxnum,cortex-x4,crc,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,ete,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,hcx,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,spe,spe-eef,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,v9.1a,v9.2a,v9a,vh,wfxt,xs") }, { str_lit("cyclone"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, { str_lit("exynos-m3"), str_lit("addr-lsl-fast,aes,alu-lsl-fast,crc,crypto,el2vmsa,el3,exynos-cheap-as-move,exynosm3,force-32bit-jump-tables,fp-armv8,fuse-address,fuse-adrp-add,fuse-aes,fuse-csel,fuse-literals,neon,perfmon,predictable-select-expensive,sha2,store-pair-suppress,use-postra-scheduler,v8a") }, { str_lit("exynos-m4"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,alu-lsl-fast,arith-bcc-fusion,arith-cbz-fusion,ccpp,crc,crypto,dotprod,el2vmsa,el3,exynos-cheap-as-move,exynosm4,force-32bit-jump-tables,fp-armv8,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-csel,fuse-literals,lor,lse,neon,pan,pan-rwv,perfmon,ras,rdm,sha2,store-pair-suppress,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh,zcz,zcz-gp") }, @@ -459,4 +486,31 @@ gb_global MicroarchFeatureList microarch_features_list[] = { { str_lit("bleeding-edge"), str_lit("atomics,bulk-memory,mutable-globals,nontrapping-fptoint,sign-ext,simd128,tail-call") }, { str_lit("generic"), str_lit("mutable-globals,sign-ext") }, { str_lit("mvp"), str_lit("") }, -}; \ No newline at end of file + // TargetArch_riscv64: + { str_lit("generic"), str_lit("64bit") }, + { str_lit("generic-rv32"), str_lit("32bit") }, + { str_lit("generic-rv64"), str_lit("64bit") }, + { str_lit("rocket"), str_lit("") }, + { str_lit("rocket-rv32"), str_lit("32bit,zicsr,zifencei") }, + { str_lit("rocket-rv64"), str_lit("64bit,zicsr,zifencei") }, + { str_lit("sifive-7-series"), str_lit("no-default-unroll,short-forward-branch-opt,sifive7") }, + { str_lit("sifive-e20"), str_lit("32bit,c,m,zicsr,zifencei") }, + { str_lit("sifive-e21"), str_lit("32bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-e24"), str_lit("32bit,a,c,f,m,zicsr,zifencei") }, + { str_lit("sifive-e31"), str_lit("32bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-e34"), str_lit("32bit,a,c,f,m,zicsr,zifencei") }, + { str_lit("sifive-e76"), str_lit("32bit,a,c,f,m,no-default-unroll,short-forward-branch-opt,sifive7,zicsr,zifencei") }, + { str_lit("sifive-p450"), str_lit("64bit,a,auipc-addi-fusion,c,conditional-cmv-fusion,d,f,fast-unaligned-access,lui-addi-fusion,m,no-default-unroll,za64rs,zba,zbb,zbs,zfhmin,zic64b,zicbom,zicbop,zicboz,ziccamoa,ziccif,zicclsm,ziccrse,zicsr,zifencei,zihintntl,zihintpause,zihpm") }, + { str_lit("sifive-p670"), str_lit("64bit,a,auipc-addi-fusion,c,conditional-cmv-fusion,d,f,fast-unaligned-access,lui-addi-fusion,m,no-default-unroll,v,za64rs,zba,zbb,zbs,zfhmin,zic64b,zicbom,zicbop,zicboz,ziccamoa,ziccif,zicclsm,ziccrse,zicsr,zifencei,zihintntl,zihintpause,zihpm,zvbb,zvbc,zve32f,zve32x,zve64d,zve64f,zve64x,zvkb,zvkg,zvkn,zvknc,zvkned,zvkng,zvknhb,zvks,zvksc,zvksed,zvksg,zvksh,zvkt,zvl128b,zvl32b,zvl64b") }, + { str_lit("sifive-s21"), str_lit("64bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-s51"), str_lit("64bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-s54"), str_lit("64bit,a,c,d,f,m,zicsr,zifencei") }, + { str_lit("sifive-s76"), str_lit("64bit,a,c,d,f,m,no-default-unroll,short-forward-branch-opt,sifive7,zicsr,zifencei,zihintpause") }, + { str_lit("sifive-u54"), str_lit("64bit,a,c,d,f,m,zicsr,zifencei") }, + { str_lit("sifive-u74"), str_lit("64bit,a,c,d,f,m,no-default-unroll,short-forward-branch-opt,sifive7,zicsr,zifencei") }, + { str_lit("sifive-x280"), str_lit("64bit,a,c,d,dlen-factor-2,f,m,no-default-unroll,short-forward-branch-opt,sifive7,v,zba,zbb,zfh,zfhmin,zicsr,zifencei,zve32f,zve32x,zve64d,zve64f,zve64x,zvfh,zvfhmin,zvl128b,zvl256b,zvl32b,zvl512b,zvl64b") }, + { str_lit("syntacore-scr1-base"), str_lit("32bit,c,no-default-unroll,zicsr,zifencei") }, + { str_lit("syntacore-scr1-max"), str_lit("32bit,c,m,no-default-unroll,zicsr,zifencei") }, + { str_lit("veyron-v1"), str_lit("64bit,a,auipc-addi-fusion,c,d,f,ld-add-fusion,lui-addi-fusion,m,shifted-zextw-fusion,ventana-veyron,xventanacondops,zba,zbb,zbc,zbs,zexth-fusion,zextw-fusion,zicbom,zicbop,zicboz,zicntr,zicsr,zifencei,zihintpause,zihpm") }, + { str_lit("xiangshan-nanhu"), str_lit("64bit,a,c,d,f,m,svinval,zba,zbb,zbc,zbkb,zbkc,zbkx,zbs,zicbom,zicboz,zicsr,zifencei,zkn,zknd,zkne,zknh,zksed,zksh") }, +}; diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ba42f891b..aa6b087fe 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -155,6 +155,11 @@ gb_internal bool does_require_msgSend_stret(Type *return_type) { return false; } + // No objc here so this doesn't matter, right? + if (build_context.metrics.arch == TargetArch_riscv64) { + return false; + } + // if (build_context.metrics.arch == TargetArch_arm32) { // i64 struct_limit = type_size_of(t_uintptr); // // NOTE(bill): This is technically wrong @@ -470,8 +475,8 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan } // Integer only - case BuiltinProc_simd_add_sat: - case BuiltinProc_simd_sub_sat: + case BuiltinProc_simd_saturating_add: + case BuiltinProc_simd_saturating_sub: case BuiltinProc_simd_bit_and: case BuiltinProc_simd_bit_or: case BuiltinProc_simd_bit_xor: @@ -501,8 +506,8 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan Type *elem = base_array_type(x.type); switch (id) { - case BuiltinProc_simd_add_sat: - case BuiltinProc_simd_sub_sat: + case BuiltinProc_simd_saturating_add: + case BuiltinProc_simd_saturating_sub: if (!is_type_integer(elem)) { gbString xs = type_to_string(x.type); error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); @@ -663,6 +668,91 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return true; } + case BuiltinProc_simd_gather: + case BuiltinProc_simd_scatter: + case BuiltinProc_simd_masked_load: + case BuiltinProc_simd_masked_store: + case BuiltinProc_simd_masked_expand_load: + case BuiltinProc_simd_masked_compress_store: + { + // gather (ptr: #simd[N]rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T + // scatter(ptr: #simd[N]rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) + + // masked_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T + // masked_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) + // masked_expand_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T + // masked_compress_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) + + Operand ptr = {}; + Operand values = {}; + Operand mask = {}; + check_expr(c, &ptr, ce->args[0]); if (ptr.mode == Addressing_Invalid) return false; + check_expr(c, &values, ce->args[1]); if (values.mode == Addressing_Invalid) return false; + check_expr(c, &mask, ce->args[2]); if (mask.mode == Addressing_Invalid) return false; + if (!is_type_simd_vector(values.type)) { error(values.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; } + if (!is_type_simd_vector(mask.type)) { error(mask.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; } + + if (id == BuiltinProc_simd_gather || id == BuiltinProc_simd_scatter) { + if (!is_type_simd_vector(ptr.type)) { error(ptr.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; } + Type *ptr_elem = base_array_type(ptr.type); + if (!is_type_rawptr(ptr_elem)) { + gbString s = type_to_string(ptr.type); + error(ptr.expr, "Expected a simd vector of 'rawptr' for the addresses, got %s", s); + gb_string_free(s); + return false; + } + } else { + if (!is_type_pointer(ptr.type)) { + gbString s = type_to_string(ptr.type); + error(ptr.expr, "Expected a pointer type for the address, got %s", s); + gb_string_free(s); + return false; + } + } + Type *mask_elem = base_array_type(mask.type); + + if (!is_type_integer(mask_elem) && !is_type_boolean(mask_elem)) { + gbString s = type_to_string(mask.type); + error(mask.expr, "Expected a simd vector of integers or booleans for the mask, got %s", s); + gb_string_free(s); + return false; + } + + if (id == BuiltinProc_simd_gather || id == BuiltinProc_simd_scatter) { + i64 ptr_count = get_array_type_count(ptr.type); + i64 values_count = get_array_type_count(values.type); + i64 mask_count = get_array_type_count(mask.type); + if (ptr_count != values_count || + values_count != mask_count || + mask_count != ptr_count) { + gbString s = type_to_string(mask.type); + error(mask.expr, "All simd vectors must be of the same length, got %lld vs %lld vs %lld", cast(long long)ptr_count, cast(long long)values_count, cast(long long)mask_count); + gb_string_free(s); + return false; + } + } else { + i64 values_count = get_array_type_count(values.type); + i64 mask_count = get_array_type_count(mask.type); + if (values_count != mask_count) { + gbString s = type_to_string(mask.type); + error(mask.expr, "All simd vectors must be of the same length, got %lld vs %lld", cast(long long)values_count, cast(long long)mask_count); + gb_string_free(s); + return false; + } + } + + if (id == BuiltinProc_simd_gather || + id == BuiltinProc_simd_masked_load || + id == BuiltinProc_simd_masked_expand_load) { + operand->mode = Addressing_Value; + operand->type = values.type; + } else { + operand->mode = Addressing_NoValue; + operand->type = nullptr; + } + return true; + } + case BuiltinProc_simd_extract: { Operand x = {}; @@ -775,6 +865,29 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return true; } + case BuiltinProc_simd_reduce_any: + case BuiltinProc_simd_reduce_all: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_boolean(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with a boolean element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_untyped_bool; + return true; + } + case BuiltinProc_simd_shuffle: { @@ -1679,7 +1792,10 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o error(call, "'#assert' expects either 1 or 2 arguments, got %td", ce->args.count); return false; } - if (!is_type_boolean(operand->type) || operand->mode != Addressing_Constant) { + + // operand->type can be nil if the condition is a procedure, for example: #assert(assert()) + // So let's check it before we use it, so we get the same error as if we wrote `#exists(assert()) + if (operand->type == nullptr || !is_type_boolean(operand->type) || operand->mode != Addressing_Constant) { gbString str = expr_to_string(ce->args[0]); error(call, "'%s' is not a constant boolean", str); gb_string_free(str); @@ -1949,6 +2065,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return ok; } + if (BuiltinProc__atomic_begin < id && id < BuiltinProc__atomic_end) { + if (build_context.metrics.arch == TargetArch_riscv64) { + if (!check_target_feature_is_enabled(str_lit("a"), nullptr)) { + error(call, "missing required target feature \"a\" for atomics, enable it by setting a different -microarch or explicitly adding it through -target-features"); + } + } + } + switch (id) { default: GB_PANIC("Implement built-in procedure: %.*s", LIT(builtin_name)); @@ -2474,7 +2598,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As arg_count++; } - if (arg_count > max_count) { + if (false && arg_count > max_count) { error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count); return false; } else if (arg_count < 2) { @@ -3885,6 +4009,23 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } + case BuiltinProc_constant_log2: { + Operand o = {}; + check_expr(c, &o, ce->args[0]); + + if (!is_type_integer(o.type) && (o.mode != Addressing_Constant)) { + error(ce->args[0], "Expected a constant integer for '%.*s'", LIT(builtin_name)); + return false; + } + + int log2 = big_int_log2(&o.value.value_integer); + + operand->mode = Addressing_Constant; + operand->value = exact_value_i64(cast(i64)log2); + operand->type = t_untyped_integer; + break; + } + case BuiltinProc_soa_struct: { Operand x = {}; Operand y = {}; @@ -4316,8 +4457,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; - case BuiltinProc_add_sat: - case BuiltinProc_sub_sat: + case BuiltinProc_saturating_add: + case BuiltinProc_saturating_sub: { Operand x = {}; Operand y = {}; @@ -5076,6 +5217,16 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (sz >= 64) { + if (is_type_unsigned(x.type)) { + add_package_dependency(c, "runtime", "umodti3", true); + add_package_dependency(c, "runtime", "udivti3", true); + } else { + add_package_dependency(c, "runtime", "modti3", true); + add_package_dependency(c, "runtime", "divti3", true); + } + } + operand->type = x.type; operand->mode = Addressing_Value; } @@ -5549,6 +5700,59 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } break; + + case BuiltinProc_type_has_shared_fields: + { + Type *u = check_type(c, ce->args[0]); + Type *ut = base_type(u); + if (ut == nullptr || ut == t_invalid) { + error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + if (ut->kind != Type_Struct || ut->Struct.soa_kind != StructSoa_None) { + gbString t = type_to_string(ut); + error(ce->args[0], "Expected a struct type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + Type *v = check_type(c, ce->args[1]); + Type *vt = base_type(v); + if (vt == nullptr || vt == t_invalid) { + error(ce->args[1], "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + if (vt->kind != Type_Struct || vt->Struct.soa_kind != StructSoa_None) { + gbString t = type_to_string(vt); + error(ce->args[1], "Expected a struct type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + bool is_shared = true; + + for (Entity *v_field : vt->Struct.fields) { + bool found = false; + for (Entity *u_field : ut->Struct.fields) { + if (v_field->token.string == u_field->token.string && + are_types_identical(v_field->type, u_field->type)) { + found = true; + break; + } + } + + if (!found) { + is_shared = false; + break; + } + } + + operand->mode = Addressing_Constant; + operand->value = exact_value_bool(is_shared); + operand->type = t_untyped_bool; + break; + } + case BuiltinProc_type_field_type: { Operand op = {}; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index a1436fe03..3b532a727 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -180,6 +180,8 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e original_entity->flags |= EntityFlag_Overridden; original_entity->type = new_entity->type; + original_entity->kind = new_entity->kind; + original_entity->decl_info = new_entity->decl_info; original_entity->aliased_of = new_entity; original_entity->identifier.store(new_entity->identifier); @@ -193,7 +195,7 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e // 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_memmove(cast(u8 *)original_entity + offset, cast(u8 *)new_entity + offset, size); } gb_internal bool check_override_as_type_due_to_aliasing(CheckerContext *ctx, Entity *e, Entity *entity, Ast *init, Type *named_type) { @@ -689,6 +691,13 @@ gb_internal bool sig_compare(TypeCheckSig *a, TypeCheckSig *b, Type *x, Type *y) } gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { + if (is_type_bit_set(x)) { + x = bit_set_to_int(x); + } + if (is_type_bit_set(y)) { + y = bit_set_to_int(y); + } + if (sig_compare(is_type_pointer, x, y)) { return true; } @@ -735,6 +744,14 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { return true; } + if (sig_compare(is_type_slice, x, y)) { + Type *s1 = core_type(x); + Type *s2 = core_type(y); + if (signature_parameter_similar_enough(s1->Slice.elem, s2->Slice.elem)) { + return true; + } + } + return are_types_identical(x, y); } @@ -751,16 +768,56 @@ gb_internal bool are_signatures_similar_enough(Type *a_, Type *b_) { if (a->result_count != b->result_count) { return false; } + + if (a->c_vararg != b->c_vararg) { + return false; + } + + if (a->variadic != b->variadic) { + return false; + } + + if (a->variadic && a->variadic_index != b->variadic_index) { + return false; + } + for (isize i = 0; i < a->param_count; i++) { Type *x = core_type(a->params->Tuple.variables[i]->type); Type *y = core_type(b->params->Tuple.variables[i]->type); + + if (x->kind == Type_BitSet && x->BitSet.underlying) { + x = core_type(x->BitSet.underlying); + } + if (y->kind == Type_BitSet && y->BitSet.underlying) { + y = core_type(y->BitSet.underlying); + } + + // Allow a `#c_vararg args: ..any` with `#c_vararg args: ..foo`. + if (a->variadic && i == a->variadic_index) { + GB_ASSERT(x->kind == Type_Slice); + GB_ASSERT(y->kind == Type_Slice); + Type *x_elem = core_type(x->Slice.elem); + Type *y_elem = core_type(y->Slice.elem); + if (is_type_any(x_elem) || is_type_any(y_elem)) { + continue; + } + } + if (!signature_parameter_similar_enough(x, y)) { return false; } } for (isize i = 0; i < a->result_count; i++) { - Type *x = base_type(a->results->Tuple.variables[i]->type); - Type *y = base_type(b->results->Tuple.variables[i]->type); + Type *x = core_type(a->results->Tuple.variables[i]->type); + Type *y = core_type(b->results->Tuple.variables[i]->type); + + if (x->kind == Type_BitSet && x->BitSet.underlying) { + x = core_type(x->BitSet.underlying); + } + if (y->kind == Type_BitSet && y->BitSet.underlying) { + y = core_type(y->BitSet.underlying); + } + if (!signature_parameter_similar_enough(x, y)) { return false; } @@ -1122,7 +1179,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { e->deprecated_message = ac.deprecated_message; e->warning_message = ac.warning_message; - ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix,ac.link_suffix); + ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix); if (ac.has_disabled_proc) { if (ac.disabled_proc) { e->flags |= EntityFlag_Disabled; @@ -1219,6 +1276,8 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } else { pt->require_results = true; } + } else if (d->foreign_require_results && pt->result_count != 0) { + pt->require_results = true; } if (ac.link_name.len > 0) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 855f1f183..dfc2ffdea 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1071,16 +1071,19 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ return; } + // Grab definite or indefinite article matching `context_name`, or "" if not found. + String article = error_article(context_name); + if (is_type_untyped(operand->type)) { Type *target_type = type; if (type == nullptr || is_type_any(type)) { if (type == nullptr && is_type_untyped_uninit(operand->type)) { - error(operand->expr, "Use of --- in %.*s", LIT(context_name)); + error(operand->expr, "Use of --- in %.*s%.*s", LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; return; } if (type == nullptr && is_type_untyped_nil(operand->type)) { - error(operand->expr, "Use of untyped nil in %.*s", LIT(context_name)); + error(operand->expr, "Use of untyped nil in %.*s%.*s", LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; return; } @@ -1135,9 +1138,10 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ // TODO(bill): is this a good enough error message? error(operand->expr, - "Cannot assign overloaded procedure group '%s' to '%s' in %.*s", + "Cannot assign overloaded procedure group '%s' to '%s' in %.*s%.*s", expr_str, op_type_str, + LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; } @@ -1163,21 +1167,28 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ switch (operand->mode) { case Addressing_Builtin: error(operand->expr, - "Cannot assign built-in procedure '%s' in %.*s", + "Cannot assign built-in procedure '%s' to %.*s%.*s", expr_str, + LIT(article), LIT(context_name)); break; case Addressing_Type: if (is_type_polymorphic(operand->type)) { error(operand->expr, - "Cannot assign '%s' which is a polymorphic type in %.*s", + "Cannot assign '%s', a polymorphic type, to %.*s%.*s", op_type_str, + LIT(article), LIT(context_name)); } else { + ERROR_BLOCK(); error(operand->expr, - "Cannot assign '%s' which is a type in %.*s", + "Cannot assign '%s', a type, to %.*s%.*s", op_type_str, + LIT(article), LIT(context_name)); + if (type && are_types_identical(type, t_any)) { + error_line("\tSuggestion: 'typeid_of(%s)'", expr_str); + } } break; default: @@ -1203,10 +1214,11 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ ERROR_BLOCK(); error(operand->expr, - "Cannot assign value '%s' of type '%s%s' to '%s%s' in %.*s", + "Cannot assign value '%s' of type '%s%s' to '%s%s' in %.*s%.*s", expr_str, op_type_str, op_type_extra, type_str, type_extra, + LIT(article), LIT(context_name)); check_assignment_error_suggestion(c, operand, type); @@ -1237,6 +1249,24 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ error_line("\t Got: %s\n", s_got); gb_string_free(s_got); gb_string_free(s_expected); + } else if (same_inputs && same_outputs && + x->Proc.diverging != y->Proc.diverging) { + + gbString s_expected = type_to_string(y); + if (y->Proc.diverging) { + s_expected = gb_string_appendc(s_expected, " -> !"); + } + + gbString s_got = type_to_string(x); + if (x->Proc.diverging) { + s_got = gb_string_appendc(s_got, " -> !"); + } + + error_line("\tNote: One of the procedures is diverging while the other isn't\n"); + error_line("\t Expected: %s\n", s_expected); + error_line("\t Got: %s\n", s_got); + gb_string_free(s_got); + gb_string_free(s_expected); } else if (same_inputs && !same_outputs) { gbString s_expected = type_to_string(y->Proc.results); gbString s_got = type_to_string(x->Proc.results); @@ -2314,14 +2344,24 @@ gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, } BigInt *bi = &o->value.value_integer; if (is_type_unsigned(type)) { + BigInt one = big_int_make_u64(1); + BigInt max_size = big_int_make_u64(1); + BigInt bits = big_int_make_i64(bit_size); + big_int_shl_eq(&max_size, &bits); + big_int_sub_eq(&max_size, &one); + if (big_int_is_neg(bi)) { error_line("\tA negative value cannot be represented by the unsigned integer type '%s'\n", b); + BigInt dst = {}; + big_int_neg(&dst, bi); + if (big_int_cmp(&dst, &max_size) < 0) { + big_int_sub_eq(&dst, &one); + String dst_str = big_int_to_string(temporary_allocator(), &dst); + gbString t = type_to_string(type); + error_line("\tSuggestion: ~%s(%.*s)\n", t, LIT(dst_str)); + gb_string_free(t); + } } else { - BigInt one = big_int_make_u64(1); - BigInt max_size = big_int_make_u64(1); - BigInt bits = big_int_make_i64(bit_size); - big_int_shl_eq(&max_size, &bits); - big_int_sub_eq(&max_size, &one); String max_size_str = big_int_to_string(temporary_allocator(), &max_size); if (size_changed) { @@ -2535,6 +2575,84 @@ gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) { return o->mode != Addressing_Variable && o->mode != Addressing_SoaVariable; } +gb_internal ExactValue exact_bit_set_all_set_mask(Type *type) { + type = base_type(type); + GB_ASSERT(type->kind == Type_BitSet); + + i64 lower = type->BitSet.lower; + i64 upper = type->BitSet.upper; + Type *elem = type->BitSet.elem; + Type *underlying = type->BitSet.underlying; + bool is_backed = underlying != nullptr; + gb_unused(is_backed); + + BigInt b_lower = {}; + BigInt b_upper = {}; + big_int_from_i64(&b_lower, lower); + big_int_from_i64(&b_upper, upper); + + + BigInt one = {}; + big_int_from_u64(&one, 1); + + BigInt mask = {}; + + if (elem == nullptr) { + big_int_from_i64(&mask, -1); + } else if (is_type_enum(elem)) { + Type *e = base_type(elem); + GB_ASSERT(e->kind == Type_Enum); + gb_unused(e); + + if ((big_int_cmp(&e->Enum.min_value->value_integer, &b_lower) == 0 || is_backed) && + big_int_cmp(&e->Enum.max_value->value_integer, &b_upper) == 0) { + + i64 lower_base = is_backed ? gb_min(0, lower) : lower; + BigInt b_lower_base = {}; + big_int_from_i64(&b_lower_base, lower_base); + + for (Entity *f : e->Enum.fields) { + if (f->kind != Entity_Constant) { + continue; + } + if (f->Constant.value.kind != ExactValue_Integer) { + continue; + } + + BigInt shift_amount = f->Constant.value.value_integer; + big_int_sub_eq(&shift_amount, &b_lower_base); + + + BigInt value = {}; + big_int_shl(&value, &one, &shift_amount); + + big_int_or(&mask, &mask, &value); + } + + } else { + // TODO(bill): enum range based"); + big_int_from_i64(&mask, -1); + } + } else { + i64 lower_base = lower; + for (i64 x = lower; x <= upper; x++) { + BigInt shift_amount = {}; + big_int_from_i64(&shift_amount, x - lower_base); + + BigInt value = {}; + big_int_shl(&value, &one, &shift_amount); + + big_int_or(&mask, &mask, &value); + } + } + + + ExactValue res = {}; + res.kind = ExactValue_Integer; + res.value_integer = mask; + return res; +} + gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *node) { switch (op.kind) { case Token_And: { // Pointer address @@ -2674,6 +2792,10 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * } o->value = exact_unary_operator_value(op.kind, o->value, precision, is_unsigned); + if (op.kind == Token_Xor && is_type_bit_set(type)) { + ExactValue mask = exact_bit_set_all_set_mask(type); + o->value = exact_binary_operator_value(Token_And, o->value, mask); + } if (is_type_typed(type)) { if (node != nullptr) { @@ -3020,7 +3142,7 @@ gb_internal void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *nod x->mode = Addressing_Value; if (type_hint) { if (is_type_integer(type_hint)) { - x->type = type_hint; + convert_to_typed(c, x, type_hint); } else { gbString x_str = expr_to_string(x->expr); gbString to_type = type_to_string(type_hint); @@ -3746,10 +3868,10 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ // NOTE(bill): Allow comparisons between types if (is_ise_expr(be->left)) { // Evalute the right before the left for an '.X' expression - check_expr_or_type(c, y, be->right, type_hint); + check_expr_or_type(c, y, be->right, nullptr /* ignore type hint */); check_expr_or_type(c, x, be->left, y->type); } else { - check_expr_or_type(c, x, be->left, type_hint); + check_expr_or_type(c, x, be->left, nullptr /* ignore type hint */); check_expr_or_type(c, y, be->right, x->type); } bool xt = x->mode == Addressing_Type; @@ -3917,6 +4039,60 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ return; } + bool REQUIRE = true; + Type *bt = base_type(x->type); + if (op.kind == Token_Mod || op.kind == Token_ModEq || + op.kind == Token_ModMod || op.kind == Token_ModModEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_u128: add_package_dependency(c, "runtime", "umodti3", REQUIRE); break; + case Basic_i128: add_package_dependency(c, "runtime", "modti3", REQUIRE); break; + } + } else if (op.kind == Token_Quo || op.kind == Token_QuoEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_complex32: add_package_dependency(c, "runtime", "quo_complex32"); break; + case Basic_complex64: add_package_dependency(c, "runtime", "quo_complex64"); break; + case Basic_complex128: add_package_dependency(c, "runtime", "quo_complex128"); break; + case Basic_quaternion64: add_package_dependency(c, "runtime", "quo_quaternion64"); break; + case Basic_quaternion128: add_package_dependency(c, "runtime", "quo_quaternion128"); break; + case Basic_quaternion256: add_package_dependency(c, "runtime", "quo_quaternion256"); break; + + case Basic_u128: add_package_dependency(c, "runtime", "udivti3", REQUIRE); break; + case Basic_i128: add_package_dependency(c, "runtime", "divti3", REQUIRE); break; + } + } else if (op.kind == Token_Mul || op.kind == Token_MulEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_quaternion64: add_package_dependency(c, "runtime", "mul_quaternion64"); break; + case Basic_quaternion128: add_package_dependency(c, "runtime", "mul_quaternion128"); break; + case Basic_quaternion256: add_package_dependency(c, "runtime", "mul_quaternion256"); break; + + + case Basic_u128: + case Basic_i128: + if (is_arch_wasm()) { + add_package_dependency(c, "runtime", "__multi3", REQUIRE); + } + break; + } + } else if (op.kind == Token_Shl || op.kind == Token_ShlEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_u128: + case Basic_i128: + if (is_arch_wasm()) { + add_package_dependency(c, "runtime", "__ashlti3", REQUIRE); + } + break; + } + } else if (op.kind == Token_Shr || op.kind == Token_ShrEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_u128: + case Basic_i128: + if (is_arch_wasm()) { + add_package_dependency(c, "runtime", "__lshrti3", REQUIRE); + } + break; + } + } + if (token_is_shift(op.kind)) { check_shift(c, x, y, node, type_hint); return; @@ -4085,52 +4261,6 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ return; } - bool REQUIRE = true; - - Type *bt = base_type(x->type); - if (op.kind == Token_Mod || op.kind == Token_ModEq || - op.kind == Token_ModMod || op.kind == Token_ModModEq) { - if (bt->kind == Type_Basic) switch (bt->Basic.kind) { - case Basic_u128: add_package_dependency(c, "runtime", "umodti3", REQUIRE); break; - case Basic_i128: add_package_dependency(c, "runtime", "modti3", REQUIRE); break; - } - } else if (op.kind == Token_Quo || op.kind == Token_QuoEq) { - if (bt->kind == Type_Basic) switch (bt->Basic.kind) { - case Basic_complex32: add_package_dependency(c, "runtime", "quo_complex32"); break; - case Basic_complex64: add_package_dependency(c, "runtime", "quo_complex64"); break; - case Basic_complex128: add_package_dependency(c, "runtime", "quo_complex128"); break; - case Basic_quaternion64: add_package_dependency(c, "runtime", "quo_quaternion64"); break; - case Basic_quaternion128: add_package_dependency(c, "runtime", "quo_quaternion128"); break; - case Basic_quaternion256: add_package_dependency(c, "runtime", "quo_quaternion256"); break; - - case Basic_u128: add_package_dependency(c, "runtime", "udivti3", REQUIRE); break; - case Basic_i128: add_package_dependency(c, "runtime", "divti3", REQUIRE); break; - } - } else if (op.kind == Token_Mul || op.kind == Token_MulEq) { - if (bt->kind == Type_Basic) switch (bt->Basic.kind) { - case Basic_quaternion64: add_package_dependency(c, "runtime", "mul_quaternion64"); break; - case Basic_quaternion128: add_package_dependency(c, "runtime", "mul_quaternion128"); break; - case Basic_quaternion256: add_package_dependency(c, "runtime", "mul_quaternion256"); break; - - - case Basic_u128: - case Basic_i128: - if (is_arch_wasm()) { - add_package_dependency(c, "runtime", "__multi3", REQUIRE); - } - break; - } - } else if (op.kind == Token_Shl || op.kind == Token_ShlEq) { - if (bt->kind == Type_Basic) switch (bt->Basic.kind) { - case Basic_u128: - case Basic_i128: - if (is_arch_wasm()) { - add_package_dependency(c, "runtime", "__ashlti3", REQUIRE); - } - break; - } - } - x->mode = Addressing_Value; } @@ -4614,7 +4744,8 @@ gb_internal bool check_index_value(CheckerContext *c, Type *main_type, bool open check_expr_with_type_hint(c, &operand, index_value, type_hint); if (operand.mode == Addressing_Invalid) { if (value) *value = 0; - return false; + // NOTE(bill): return true here to propagate the errors better + return true; } Type *index_type = t_int; @@ -4835,7 +4966,7 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v TypeAndValue tav = fv->value->tav; if (success_) *success_ = true; if (finish_) *finish_ = false; - return tav.value;; + return tav.value; } } @@ -4910,7 +5041,6 @@ gb_internal ExactValue get_constant_field(CheckerContext *c, Operand const *oper return value; } } - if (success_) *success_ = true; return value; } else if (value.kind == ExactValue_Quaternion) { @@ -5150,7 +5280,14 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod add_entity_use(c, op_expr, e); expr_entity = e; - if (e != nullptr && e->kind == Entity_ImportName && selector->kind == Ast_Ident) { + if (e != nullptr && (e->kind == Entity_Procedure || e->kind == Entity_ProcGroup) && selector->kind == Ast_Ident) { + gbString sel_str = expr_to_string(selector); + error(node, "'%s' is not declared by by '%.*s'", sel_str, LIT(e->token.string)); + gb_string_free(sel_str); + operand->mode = Addressing_Invalid; + operand->expr = node; + return nullptr; + } else if (e != nullptr && e->kind == Entity_ImportName && selector->kind == Ast_Ident) { // IMPORTANT NOTE(bill): This is very sloppy code but it's also very fragile // It pretty much needs to be in this order and this way // If you can clean this up, please do but be really careful @@ -5158,6 +5295,14 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Scope *import_scope = e->ImportName.scope; String entity_name = selector->Ident.token.string; + if (import_scope == nullptr) { + ERROR_BLOCK(); + error(node, "'%.*s' is not imported in this file, '%.*s' is unavailable", LIT(import_name), LIT(entity_name)); + operand->mode = Addressing_Invalid; + operand->expr = node; + return nullptr; + } + check_op_expr = false; entity = scope_lookup_current(import_scope, entity_name); bool allow_builtin = false; @@ -5593,7 +5738,7 @@ gb_internal bool check_identifier_exists(Scope *s, Ast *node, bool nested = fals gb_internal bool check_no_copy_assignment(Operand const &o, String const &context) { if (o.type && is_type_no_copy(o.type)) { Ast *expr = unparen_expr(o.expr); - if (expr && o.mode != Addressing_Constant) { + if (expr && o.mode != Addressing_Constant && o.mode != Addressing_Type) { if (expr->kind == Ast_CallExpr) { // Okay } else { @@ -6227,7 +6372,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } if (param_is_variadic) { - continue; + if (!named_variadic_param) { + continue; + } } score += eval_param_and_score(c, o, e->type, err, false, e, show_error); } @@ -7564,7 +7711,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O gbString s = gb_string_make_reserve(heap_allocator(), e->token.string.len+3); s = gb_string_append_fmt(s, "%.*s(", LIT(e->token.string)); - TypeTuple *tuple = get_record_polymorphic_params(e->type); + TypeTuple *tuple = get_record_polymorphic_params(bt); if (tuple != nullptr) for_array(i, tuple->variables) { Entity *v = tuple->variables[i]; String name = v->token.string; @@ -7579,8 +7726,10 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O s = write_type_to_string(s, v->type, false); } } else if (v->kind == Entity_Constant) { - s = gb_string_append_fmt(s, "="); - s = write_exact_value_to_string(s, v->Constant.value); + if (v->Constant.value.kind != ExactValue_Invalid) { + s = gb_string_append_fmt(s, "="); + s = write_exact_value_to_string(s, v->Constant.value); + } } } s = gb_string_append_fmt(s, ")"); @@ -9996,6 +10145,22 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * is_constant = o->mode == Addressing_Constant; } + if (elem->kind == Ast_BinaryExpr) { + switch (elem->BinaryExpr.op.kind) { + case Token_Or: + { + gbString x = expr_to_string(elem->BinaryExpr.left); + gbString y = expr_to_string(elem->BinaryExpr.right); + gbString e = expr_to_string(elem); + error(elem, "Was the following intended? '%s, %s'; if not, surround the expression with parentheses '(%s)'", x, y, e); + gb_string_free(e); + gb_string_free(y); + gb_string_free(x); + } + break; + } + } + check_assignment(c, o, t->BitSet.elem, str_lit("bit_set literal")); if (o->mode == Addressing_Constant) { i64 lower = t->BitSet.lower; @@ -10004,7 +10169,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (lower <= v && v <= upper) { // okay } else { - error(elem, "Bit field value out of bounds, %lld not in the range %lld .. %lld", v, lower, upper); + gbString s = expr_to_string(o->expr); + error(elem, "Bit field value out of bounds, %s (%lld) not in the range %lld .. %lld", s, v, lower, upper); + gb_string_free(s); continue; } } @@ -10485,7 +10652,8 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, o->expr = node; return kind; } else if (ok && !is_type_matrix(t)) { - ExactValue value = type_and_value_of_expr(ie->expr).value; + TypeAndValue tav = type_and_value_of_expr(ie->expr); + ExactValue value = tav.value; o->mode = Addressing_Constant; bool success = false; bool finish = false; @@ -10762,7 +10930,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast case Token_context: { if (c->proc_name.len == 0 && c->curr_proc_sig == nullptr) { - error(node, "'context' is only allowed within procedures %p", c->curr_proc_decl); + error(node, "'context' is only allowed within procedures"); return kind; } if (unparen_expr(c->assignment_lhs_hint) == node) { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 76b6d3f40..c8717ba98 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -199,6 +199,9 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) } break; + case Ast_DeferStmt: + return check_has_break(stmt->DeferStmt.stmt, label, implicit); + case Ast_BlockStmt: return check_has_break_list(stmt->BlockStmt.stmts, label, implicit); @@ -474,8 +477,15 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O rhs->proc_group = nullptr; } } else { + Ast *ident_node = nullptr; + if (node->kind == Ast_Ident) { - ast_node(i, Ident, node); + ident_node = node; + } else if (node->kind == Ast_IndexExpr && node->IndexExpr.expr->kind == Ast_Ident) { + ident_node = node->IndexExpr.expr; + } + if (ident_node != nullptr) { + ast_node(i, Ident, ident_node); e = scope_lookup(ctx->scope, i->token.string); if (e != nullptr && e->kind == Entity_Variable) { used = (e->flags & EntityFlag_Used) != 0; // NOTE(bill): Make backup just in case @@ -817,7 +827,8 @@ gb_internal bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, if (f->kind == Entity_Variable) { Entity *uvar = alloc_entity_using_variable(e, f->token, f->type, expr); if (!is_ptr && e->flags & EntityFlag_Value) uvar->flags |= EntityFlag_Value; - if (e->flags & EntityFlag_Param) uvar->flags |= EntityFlag_Param; + if (e->flags & EntityFlag_Param) uvar->flags |= EntityFlag_Param; + if (e->flags & EntityFlag_SoaPtrField) uvar->flags |= EntityFlag_SoaPtrField; Entity *prev = scope_insert(ctx->scope, uvar); if (prev != nullptr) { gbString expr_str = expr_to_string(expr); @@ -1471,7 +1482,7 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ case_type = nullptr; } if (case_type == nullptr) { - case_type = x.type; + case_type = type_deref(x.type); } if (switch_kind == TypeSwitch_Any) { if (!is_type_untyped(case_type)) { @@ -1630,6 +1641,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) Ast *expr = unparen_expr(rs->expr); + bool is_range = false; bool is_possibly_addressable = true; isize max_val_count = 2; if (is_ast_range(expr)) { @@ -1638,6 +1650,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) Operand y = {}; is_possibly_addressable = false; + is_range = true; bool ok = check_range(ctx, expr, true, &x, &y, nullptr); if (!ok) { @@ -1685,7 +1698,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } } } - bool is_ptr = type_deref(operand.type); + bool is_ptr = is_type_pointer(type_deref(operand.type)); Type *t = base_type(type_deref(operand.type)); switch (t->kind) { @@ -1725,6 +1738,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) break; case Type_EnumeratedArray: + is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr; array_add(&vals, t->EnumeratedArray.elem); array_add(&vals, t->EnumeratedArray.index); break; @@ -1882,7 +1896,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } if (found == nullptr) { entity = alloc_entity_variable(ctx->scope, token, type, EntityState_Resolved); - entity->flags |= EntityFlag_ForValue; + if (!is_range) { + entity->flags |= EntityFlag_ForValue; + } entity->flags |= EntityFlag_Value; entity->identifier = name; entity->Variable.for_loop_parent_type = type_of_expr(expr); @@ -2489,6 +2505,16 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { continue; } Ast *expr = unparen_expr(o.expr); + while (expr->kind == Ast_CallExpr && expr->CallExpr.proc->tav.mode == Addressing_Type) { + if (expr->CallExpr.args.count != 1) { + break; + } + Ast *arg = expr->CallExpr.args[0]; + if (arg->kind == Ast_FieldValue || !are_types_identical(arg->tav.type, expr->tav.type)) { + break; + } + expr = unparen_expr(arg); + } auto unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) { gbString s = expr_to_string(o.expr); @@ -2684,6 +2710,7 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) error(bs->label, "A branch statement's label name must be an identifier"); return; } + Ast *ident = bs->label; String name = ident->Ident.token.string; Operand o = {}; @@ -2715,6 +2742,10 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) break; } + + if (ctx->in_defer) { + error(bs->label, "A labelled '%.*s' cannot be used within a 'defer'", LIT(token.string)); + } } case_end; diff --git a/src/check_type.cpp b/src/check_type.cpp index 428fe8451..3767f7666 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1781,6 +1781,11 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para success = false; } + if (default_value != nullptr) { + error(type_expr, "A variadic parameter may not have a default value"); + 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); } @@ -2309,8 +2314,28 @@ gb_internal Type *check_get_results(CheckerContext *ctx, Scope *scope, Ast *_res return tuple; } +gb_internal void check_procedure_param_polymorphic_type(CheckerContext *ctx, Type *type, Ast *type_expr) { + GB_ASSERT_NOT_NULL(type_expr); + if (type == nullptr || ctx->in_polymorphic_specialization) { return; } + if (!is_type_polymorphic_record_unspecialized(type)) { return; } + bool invalid_polymorpic_type_use = false; + switch (type_expr->kind) { + case_ast_node(pt, Ident, type_expr); + invalid_polymorpic_type_use = true; + case_end; + case_ast_node(pt, SelectorExpr, type_expr); + invalid_polymorpic_type_use = true; + case_end; + } + + if (invalid_polymorpic_type_use) { + gbString expr_str = expr_to_string(type_expr); + defer (gb_string_free(expr_str)); + error(type_expr, "Invalid use of a non-specialized polymorphic type '%s'", expr_str); + } +} // NOTE(bill): 'operands' is for generating non generic procedure type gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, Array const *operands) { @@ -2433,6 +2458,7 @@ gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc if (e->kind != Entity_Variable) { is_polymorphic = true; } else if (is_type_polymorphic(e->type)) { + check_procedure_param_polymorphic_type(c, e->type, e->Variable.type_expr); is_polymorphic = true; } @@ -2718,6 +2744,18 @@ gb_internal void check_map_type(CheckerContext *ctx, Type *type, Ast *node) { GB_ASSERT(type->kind == Type_Map); ast_node(mt, MapType, node); + if (mt->key == NULL) { + if (mt->value != NULL) { + Type *value = check_type(ctx, mt->value); + gbString str = type_to_string(value); + error(node, "Missing map key type, got 'map[]%s'", str); + gb_string_free(str); + return; + } + error(node, "Missing map key type, got 'map[]T'"); + return; + } + Type *key = check_type(ctx, mt->key); Type *value = check_type(ctx, mt->value); @@ -3171,7 +3209,7 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t } else if (name == "simd") { if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { gbString str = type_to_string(elem); - error(at->elem, "Invalid element type for #simd, expected an integer, float, or boolean with no specific endianness, got '%s'", str); + error(at->elem, "Invalid element type for #simd, expected an integer, float, boolean, or 'rawptr' with no specific endianness, got '%s'", str); gb_string_free(str); *type = alloc_type_array(elem, count, generic_type); return; diff --git a/src/checker.cpp b/src/checker.cpp index 3eae271a0..64c66c8a6 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1039,6 +1039,7 @@ gb_internal void init_universal(void) { {"arm64", TargetArch_arm64}, {"wasm32", TargetArch_wasm32}, {"wasm64p32", TargetArch_wasm64p32}, + {"riscv64", TargetArch_riscv64}, }; auto fields = add_global_enum_type(str_lit("Odin_Arch_Type"), values, gb_count_of(values)); @@ -1651,9 +1652,9 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo if (mode == Addressing_Constant || mode == Addressing_Invalid) { expr->tav.value = value; - } else if (mode == Addressing_Value && is_type_typeid(type)) { + } else if (mode == Addressing_Value && type != nullptr && is_type_typeid(type)) { expr->tav.value = value; - } else if (mode == Addressing_Value && is_type_proc(type)) { + } else if (mode == Addressing_Value && type != nullptr && is_type_proc(type)) { expr->tav.value = value; } @@ -1785,7 +1786,9 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti entity->flags |= EntityFlag_Used; if (entity_has_deferred_procedure(entity)) { Entity *deferred = entity->Procedure.deferred_procedure.entity; - add_entity_use(c, nullptr, deferred); + if (deferred != entity) { + add_entity_use(c, nullptr, deferred); + } } if (identifier == nullptr || identifier->kind != Ast_Ident) { return; @@ -2723,6 +2726,7 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { // WASM Specific str_lit("__ashlti3"), str_lit("__multi3"), + str_lit("__lshrti3"), ); FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_rtti, @@ -2991,6 +2995,9 @@ gb_internal void init_core_type_info(Checker *c) { } Entity *type_info_entity = find_core_entity(c, str_lit("Type_Info")); GB_ASSERT(type_info_entity != nullptr); + if (type_info_entity->type == nullptr) { + check_single_global_entity(c, type_info_entity, type_info_entity->decl_info); + } GB_ASSERT(type_info_entity->type != nullptr); t_type_info = type_info_entity->type; @@ -3169,8 +3176,8 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { return true; } else if (name == "link_prefix") { if (ev.kind == ExactValue_String) { - String link_prefix = ev.value_string; - if (!is_foreign_name_valid(link_prefix)) { + String link_prefix = string_trim_whitespace(ev.value_string); + if (link_prefix.len != 0 && !is_foreign_name_valid(link_prefix)) { error(elem, "Invalid link prefix: '%.*s'", LIT(link_prefix)); } else { c->foreign_context.link_prefix = link_prefix; @@ -3181,8 +3188,8 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { return true; } else if (name == "link_suffix") { if (ev.kind == ExactValue_String) { - String link_suffix = ev.value_string; - if (!is_foreign_name_valid(link_suffix)) { + String link_suffix = string_trim_whitespace(ev.value_string); + if (link_suffix.len != 0 && !is_foreign_name_valid(link_suffix)) { error(elem, "Invalid link suffix: '%.*s'", LIT(link_suffix)); } else { c->foreign_context.link_suffix = link_suffix; @@ -3209,6 +3216,12 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { } c->foreign_context.visibility_kind = kind; return true; + } else if (name == "require_results") { + if (value != nullptr) { + error(elem, "Expected no value for '%.*s'", LIT(name)); + } + c->foreign_context.require_results = true; + return true; } return false; @@ -3478,7 +3491,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { if (ev.kind == ExactValue_String) { ac->link_prefix = ev.value_string; - if (!is_foreign_name_valid(ac->link_prefix)) { + if (ac->link_prefix.len != 0 && !is_foreign_name_valid(ac->link_prefix)) { error(elem, "Invalid link prefix: %.*s", LIT(ac->link_prefix)); } } else { @@ -3490,7 +3503,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { if (ev.kind == ExactValue_String) { ac->link_suffix = ev.value_string; - if (!is_foreign_name_valid(ac->link_suffix)) { + if (ac->link_suffix.len != 0 && !is_foreign_name_valid(ac->link_suffix)) { error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix)); } } else { @@ -3763,7 +3776,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { ac->link_prefix = ev.value_string; - if (!is_foreign_name_valid(ac->link_prefix)) { + if (ac->link_prefix.len != 0 && !is_foreign_name_valid(ac->link_prefix)) { error(elem, "Invalid link prefix: %.*s", LIT(ac->link_prefix)); } } else { @@ -3774,7 +3787,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { ac->link_suffix = ev.value_string; - if (!is_foreign_name_valid(ac->link_suffix)) { + if (ac->link_suffix.len != 0 && !is_foreign_name_valid(ac->link_suffix)) { error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix)); } } else { @@ -4297,6 +4310,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { } ast_node(pl, ProcLit, init); e = alloc_entity_procedure(d->scope, token, nullptr, pl->tags); + d->foreign_require_results = c->foreign_context.require_results; if (fl != nullptr) { GB_ASSERT(fl->kind == Ast_Ident); e->Procedure.foreign_library_ident = fl; @@ -4520,7 +4534,9 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n case_end; case_ast_node(fb, ForeignBlockDecl, decl); - check_add_foreign_block_decl(c, decl); + if (curr_file != nullptr) { + array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl); + } case_end; default: @@ -4536,6 +4552,14 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n // NOTE(bill): 'when' stmts need to be handled after the other as the condition may refer to something // declared after this stmt in source if (curr_file == nullptr) { + // For 'foreign' block statements that are not in file scope. + for_array(decl_index, nodes) { + Ast *decl = nodes[decl_index]; + if (decl->kind == Ast_ForeignBlockDecl) { + check_add_foreign_block_decl(c, decl); + } + } + for_array(decl_index, nodes) { Ast *decl = nodes[decl_index]; if (decl->kind == Ast_WhenStmt) { @@ -4922,12 +4946,18 @@ gb_internal void check_add_import_decl(CheckerContext *ctx, Ast *decl) { } - if (import_name.len == 0) { + if (is_blank_ident(import_name) && !is_blank_ident(id->import_name.string)) { String invalid_name = id->fullpath; invalid_name = get_invalid_import_name(invalid_name); - error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import \"%.*s\" ", LIT(invalid_name), LIT(invalid_name)); - error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); + ERROR_BLOCK(); + + if (id->import_name.string.len > 0) { + error(token, "Import name '%.*s' cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); + } else { + error(id->token, "Import name '%.*s' is not a valid identifier", LIT(invalid_name)); + error_line("\tSuggestion: Rename the directory or explicitly set an import name like this 'import %.*s'", LIT(id->relpath.string)); + } } else { GB_ASSERT(id->import_name.pos.line != 0); id->import_name.string = import_name; @@ -5206,9 +5236,9 @@ gb_internal bool collect_file_decl(CheckerContext *ctx, Ast *decl) { case_end; case_ast_node(fb, ForeignBlockDecl, decl); - if (check_add_foreign_block_decl(ctx, decl)) { - return true; - } + GB_ASSERT(ctx->collect_delayed_decls); + decl->state_flags |= StateFlag_BeenHandled; + array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl); case_end; case_ast_node(ws, WhenStmt, decl); @@ -5491,11 +5521,20 @@ gb_internal void check_import_entities(Checker *c) { for_array(i, pkg->files) { AstFile *f = pkg->files[i]; reset_checker_context(&ctx, f, &untyped); - ctx.collect_delayed_decls = false; - correct_type_aliases_in_scope(&ctx, pkg->scope); } + for_array(i, pkg->files) { + AstFile *f = pkg->files[i]; + reset_checker_context(&ctx, f, &untyped); + + ctx.collect_delayed_decls = true; + for (Ast *decl : f->delayed_decls_queues[AstDelayQueue_ForeignBlock]) { + check_add_foreign_block_decl(&ctx, decl); + } + array_clear(&f->delayed_decls_queues[AstDelayQueue_ForeignBlock]); + } + for_array(i, pkg->files) { AstFile *f = pkg->files[i]; reset_checker_context(&ctx, f, &untyped); @@ -5676,6 +5715,18 @@ gb_internal void check_procedure_later_from_entity(Checker *c, Entity *e, char c if ((e->flags & EntityFlag_ProcBodyChecked) != 0) { return; } + if ((e->flags & EntityFlag_Overridden) != 0) { + // NOTE (zen3ger) Delay checking of a proc alias until the underlying proc is checked. + GB_ASSERT(e->aliased_of != nullptr); + GB_ASSERT(e->aliased_of->kind == Entity_Procedure); + if ((e->aliased_of->flags & EntityFlag_ProcBodyChecked) != 0) { + e->flags |= EntityFlag_ProcBodyChecked; + return; + } + // NOTE (zen3ger) A proc alias *does not* have a body and tags! + check_procedure_later(c, e->file, e->token, e->decl_info, e->type, nullptr, 0); + return; + } Type *type = base_type(e->type); if (type == t_invalid) { return; @@ -6071,6 +6122,11 @@ gb_internal void check_deferred_procedures(Checker *c) { case DeferredProcedure_in_out_by_ptr: attribute = "deferred_in_out_by_ptr"; break; } + if (src == dst) { + error(src->token, "'%.*s' cannot be used as its own %s", LIT(dst->token.string), attribute); + continue; + } + if (is_type_polymorphic(src->type) || is_type_polymorphic(dst->type)) { error(src->token, "'%s' cannot be used with a polymorphic procedure", attribute); continue; diff --git a/src/checker.hpp b/src/checker.hpp index d76e4c7d0..438156f18 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -204,9 +204,12 @@ struct DeclInfo { Array attributes; Ast * proc_lit; // Ast_ProcLit Type * gen_proc_type; // Precalculated + bool is_using; bool where_clauses_evaluated; + bool foreign_require_results; std::atomic proc_checked_state; + BlockingMutex proc_checked_mutex; isize defer_used; bool defer_use_checked; @@ -322,6 +325,7 @@ struct ForeignContext { String link_prefix; String link_suffix; EntityVisiblityKind visibility_kind; + bool require_results; }; typedef Array CheckerTypePath; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 3a2e1ce22..2dfd570e4 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -46,6 +46,8 @@ enum BuiltinProcId { BuiltinProc_has_target_feature, + BuiltinProc_constant_log2, + BuiltinProc_transpose, BuiltinProc_outer_product, BuiltinProc_hadamard_product, @@ -70,8 +72,8 @@ enum BuiltinProcId { BuiltinProc_overflow_sub, BuiltinProc_overflow_mul, - BuiltinProc_add_sat, - BuiltinProc_sub_sat, + BuiltinProc_saturating_add, + BuiltinProc_saturating_sub, BuiltinProc_sqrt, BuiltinProc_fused_mul_add, @@ -97,6 +99,7 @@ enum BuiltinProcId { BuiltinProc_prefetch_write_instruction, BuiltinProc_prefetch_write_data, +BuiltinProc__atomic_begin, BuiltinProc_atomic_type_is_lock_free, BuiltinProc_atomic_thread_fence, BuiltinProc_atomic_signal_fence, @@ -122,6 +125,7 @@ enum BuiltinProcId { BuiltinProc_atomic_compare_exchange_strong_explicit, BuiltinProc_atomic_compare_exchange_weak, BuiltinProc_atomic_compare_exchange_weak_explicit, +BuiltinProc__atomic_end, BuiltinProc_fixed_point_mul, BuiltinProc_fixed_point_div, @@ -141,8 +145,8 @@ BuiltinProc__simd_begin, BuiltinProc_simd_shl_masked, // C logic BuiltinProc_simd_shr_masked, // C logic - BuiltinProc_simd_add_sat, // saturation arithmetic - BuiltinProc_simd_sub_sat, // saturation arithmetic + BuiltinProc_simd_saturating_add, // saturation arithmetic + BuiltinProc_simd_saturating_sub, // saturation arithmetic BuiltinProc_simd_bit_and, BuiltinProc_simd_bit_or, @@ -174,6 +178,9 @@ BuiltinProc__simd_begin, BuiltinProc_simd_reduce_or, BuiltinProc_simd_reduce_xor, + BuiltinProc_simd_reduce_any, + BuiltinProc_simd_reduce_all, + BuiltinProc_simd_shuffle, BuiltinProc_simd_select, @@ -188,6 +195,12 @@ BuiltinProc__simd_begin, BuiltinProc_simd_lanes_rotate_left, BuiltinProc_simd_lanes_rotate_right, + BuiltinProc_simd_gather, + BuiltinProc_simd_scatter, + BuiltinProc_simd_masked_load, + BuiltinProc_simd_masked_store, + BuiltinProc_simd_masked_expand_load, + BuiltinProc_simd_masked_compress_store, // Platform specific SIMD intrinsics BuiltinProc_simd_x86__MM_SHUFFLE, @@ -302,6 +315,8 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc_type_map_info, BuiltinProc_type_map_cell_info, + BuiltinProc_type_has_shared_fields, + BuiltinProc__type_end, BuiltinProc_procedure_of, @@ -371,6 +386,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("has_target_feature"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("constant_log2"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("transpose"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("outer_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("hadamard_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -396,8 +413,8 @@ 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("saturating_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("saturating_sub"), 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}, @@ -423,6 +440,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("prefetch_write_instruction"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_write_data"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_type_is_lock_free"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_thread_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_signal_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, @@ -448,6 +466,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("atomic_compare_exchange_strong_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("atomic_compare_exchange_weak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("atomic_compare_exchange_weak_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_mul"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_div"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -467,8 +486,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_shl_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shr_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_saturating_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_saturating_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_bit_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_bit_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -501,6 +520,10 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_reduce_or"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_xor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_any"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_all"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_shuffle"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -515,6 +538,13 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_lanes_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_lanes_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_gather"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_scatter"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_load"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_expand_load"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_compress_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, @@ -623,6 +653,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_map_info"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_map_cell_info"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_has_shared_fields"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index ad8d9d245..835dfdff1 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -829,7 +829,6 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) OdinDocEntityIndex doc_entity_index = odin_doc_write_item(w, &w->entities, &doc_entity, &dst); map_set(&w->entity_cache, e, doc_entity_index); - Ast *type_expr = nullptr; Ast *init_expr = nullptr; Ast *decl_node = nullptr; @@ -997,7 +996,8 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { } } - for (auto const &entry : w->entity_cache) { + for (u32 i = 0; i < w->entity_cache.count; i++) { + auto entry = w->entity_cache.entries[i]; Entity *e = entry.key; OdinDocEntityIndex entity_index = entry.value; OdinDocTypeIndex type_index = odin_doc_type(w, e->type); @@ -1007,6 +1007,9 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { switch (e->kind) { case Entity_Variable: + if (w->state == OdinDocWriterState_Writing) { + GB_ASSERT(type_index != 0); + } foreign_library = odin_doc_add_entity(w, e->Variable.foreign_library); break; case Entity_Procedure: @@ -1026,8 +1029,17 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { break; } + if (w->state == OdinDocWriterState_Preparing) { + GB_ASSERT(entity_index == 0); + } else { + GB_ASSERT(entity_index != 0); + } + OdinDocEntity *dst = odin_doc_get_item(w, &w->entities, entity_index); if (dst) { + if (dst->kind == OdinDocEntity_Variable) { + GB_ASSERT(type_index != 0); + } dst->type = type_index; dst->foreign_library = foreign_library; dst->grouped_entities = grouped_entities; diff --git a/src/error.cpp b/src/error.cpp index f95123f15..1492b00c7 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -820,6 +820,35 @@ gb_internal int error_value_cmp(void const *a, void const *b) { return token_pos_cmp(x->pos, y->pos); } +gb_global String error_article_table[][2] = { + {str_lit("a "), str_lit("bit_set literal")}, + {str_lit("a "), str_lit("constant declaration")}, + {str_lit("a "), str_lit("dynamiic array literal")}, + {str_lit("a "), str_lit("map index")}, + {str_lit("a "), str_lit("map literal")}, + {str_lit("a "), str_lit("matrix literal")}, + {str_lit("a "), str_lit("polymorphic type argument")}, + {str_lit("a "), str_lit("procedure argument")}, + {str_lit("a "), str_lit("simd vector literal")}, + {str_lit("a "), str_lit("slice literal")}, + {str_lit("a "), str_lit("structure literal")}, + {str_lit("a "), str_lit("variable declaration")}, + {str_lit("an "), str_lit("'any' literal")}, + {str_lit("an "), str_lit("array literal")}, + {str_lit("an "), str_lit("enumerated array literal")}, + +}; + +// Returns definite or indefinite article matching `context_name`, or "" if not found. +gb_internal String error_article(String context_name) { + for (int i = 0; i < gb_count_of(error_article_table); i += 1) { + if (context_name == error_article_table[i][1]) { + return error_article_table[i][0]; + } + } + return str_lit(""); +} + gb_internal bool errors_already_printed = false; gb_internal void print_all_errors(void) { diff --git a/src/gb/gb.h b/src/gb/gb.h index 38dabc9bd..0e65696e2 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -39,7 +39,7 @@ extern "C" { #endif #endif -#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) || defined(__aarch64__) +#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) || defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) #ifndef GB_ARCH_64_BIT #define GB_ARCH_64_BIT 1 #endif @@ -144,6 +144,13 @@ extern "C" { #define GB_CACHE_LINE_SIZE 64 #endif +#elif defined(__riscv) + #ifndef GB_CPU_RISCV + #define GB_CPU_RISCV 1 + #endif + #ifndef GB_CACHE_LINE_SIZE + #define GB_CACHE_LINE_SIZE 64 + #endif #else #error Unknown CPU Type #endif @@ -2562,7 +2569,7 @@ gb_inline void *gb_memcopy(void *dest, void const *source, isize n) { void *dest_copy = dest; __asm__ __volatile__("rep movsb" : "+D"(dest_copy), "+S"(source), "+c"(n) : : "memory"); -#elif defined(GB_CPU_ARM) +#elif defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) u8 *s = cast(u8 *)source; u8 *d = cast(u8 *)dest; for (isize i = 0; i < n; i++) { @@ -6267,6 +6274,12 @@ gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); return virtual_timer_value; } +#elif defined(__riscv) + gb_inline u64 gb_rdtsc(void) { + u64 result = 0; + __asm__ volatile("rdcycle %0" : "=r"(result)); + return result; + } #else #warning "gb_rdtsc not supported" gb_inline u64 gb_rdtsc(void) { return 0; } diff --git a/src/linker.cpp b/src/linker.cpp index 046e72d0e..faca28932 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -388,6 +388,12 @@ gb_internal i32 linker_stage(LinkerData *gen) { } else { timings_start_section(timings, str_lit("ld-link")); + // Link using `clang`, unless overridden by `ODIN_CLANG_PATH` environment variable. + const char* clang_path = gb_get_env("ODIN_CLANG_PATH", permanent_allocator()); + if (clang_path == NULL) { + clang_path = "clang"; + } + // NOTE(vassvik): get cwd, for used for local shared libs linking, since those have to be relative to the exe char cwd[256]; #if !defined(GB_SYSTEM_WINDOWS) @@ -458,7 +464,20 @@ gb_internal i32 linker_stage(LinkerData *gen) { } #endif // GB_ARCH_*_BIT - if (is_osx) { + if (build_context.metrics.arch == TargetArch_riscv64) { + result = system_exec_command_line_app("clang", + "%s \"%.*s\" " + "-c -o \"%.*s\" " + "-target %.*s -march=rv64gc " + "%.*s " + "", + clang_path, + LIT(asm_file), + LIT(obj_file), + LIT(build_context.metrics.target_triplet), + LIT(build_context.extra_assembler_flags) + ); + } else if (is_osx) { // `as` comes with MacOS. result = system_exec_command_line_app("as", "as \"%.*s\" " @@ -592,7 +611,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); } - } else if (build_context.metrics.os != TargetOs_openbsd && build_context.metrics.os != TargetOs_haiku) { + } else if (build_context.metrics.os != TargetOs_openbsd && build_context.metrics.os != TargetOs_haiku && build_context.metrics.arch != TargetArch_riscv64) { // OpenBSD and Haiku default to PIE executable. do not pass -no-pie for it. link_settings = gb_string_appendc(link_settings, "-no-pie "); } @@ -635,12 +654,6 @@ gb_internal i32 linker_stage(LinkerData *gen) { } } - // Link using `clang`, unless overridden by `ODIN_CLANG_PATH` environment variable. - const char* clang_path = gb_get_env("ODIN_CLANG_PATH", permanent_allocator()); - if (clang_path == NULL) { - clang_path = "clang"; - } - gbString link_command_line = gb_string_make(heap_allocator(), clang_path); defer (gb_string_free(link_command_line)); diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index c21cd0a46..aa5c4dc60 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -332,7 +332,7 @@ gb_internal i64 lb_alignof(LLVMTypeRef type) { } -#define LB_ABI_INFO(name) lbFunctionType *name(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, ProcCallingConvention calling_convention, Type *original_type) +#define LB_ABI_INFO(name) lbFunctionType *name(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, ProcCallingConvention calling_convention, Type *original_type) typedef LB_ABI_INFO(lbAbiInfoType); #define LB_ABI_COMPUTE_RETURN_TYPE(name) lbArgType name(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple) @@ -380,6 +380,7 @@ namespace lbAbi386 { gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -461,6 +462,7 @@ namespace lbAbiAmd64Win64 { gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -571,6 +573,7 @@ namespace lbAbiAmd64SysV { gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->calling_convention = calling_convention; @@ -1009,6 +1012,7 @@ namespace lbAbiArm64 { gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -1243,6 +1247,7 @@ namespace lbAbiWasm { enum {MAX_DIRECT_STRUCT_SIZE = 32}; gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->calling_convention = calling_convention; @@ -1408,6 +1413,7 @@ namespace lbAbiArm32 { gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention); @@ -1485,8 +1491,256 @@ namespace lbAbiArm32 { } }; +namespace lbAbiRiscv64 { + + gb_internal bool is_register(LLVMTypeRef type) { + LLVMTypeKind kind = LLVMGetTypeKind(type); + switch (kind) { + case LLVMIntegerTypeKind: + case LLVMHalfTypeKind: + case LLVMFloatTypeKind: + case LLVMDoubleTypeKind: + case LLVMPointerTypeKind: + return true; + } + return false; + } + + gb_internal bool is_float(LLVMTypeRef type) { + LLVMTypeKind kind = LLVMGetTypeKind(type); + switch (kind) { + case LLVMHalfTypeKind: + case LLVMFloatTypeKind: + case LLVMDoubleTypeKind: + return true; + default: + return false; + } + } + + gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type) { + LLVMAttributeRef attr = nullptr; + LLVMTypeRef i1 = LLVMInt1TypeInContext(c); + if (type == i1) { + attr = lb_create_enum_attribute(c, "zeroext"); + } + return lb_arg_type_direct(type, nullptr, nullptr, attr); + } + + gb_internal void flatten(lbModule *m, Array *fields, LLVMTypeRef type, bool with_padding) { + LLVMTypeKind kind = LLVMGetTypeKind(type); + switch (kind) { + case LLVMStructTypeKind: { + if (LLVMIsPackedStruct(type)) { + array_add(fields, type); + break; + } + + if (!with_padding) { + auto field_remapping = map_get(&m->struct_field_remapping, cast(void *)type); + if (field_remapping) { + auto remap = *field_remapping; + for_array(i, remap) { + flatten(m, fields, LLVMStructGetTypeAtIndex(type, remap[i]), with_padding); + } + break; + } else { + debugf("no field mapping for type: %s\n", LLVMPrintTypeToString(type)); + } + } + + unsigned elem_count = LLVMCountStructElementTypes(type); + for (unsigned i = 0; i < elem_count; i += 1) { + flatten(m, fields, LLVMStructGetTypeAtIndex(type, i), with_padding); + } + break; + } + case LLVMArrayTypeKind: { + unsigned len = LLVMGetArrayLength(type); + LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); + for (unsigned i = 0; i < len; i += 1) { + flatten(m, fields, elem, with_padding); + } + break; + } + default: + array_add(fields, type); + } + } + + gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *odin_type) { + LLVMContextRef c = m->ctx; + + int xlen = 8; // 8 byte int register size for riscv64. + + // NOTE: we are requiring both of these to be enabled so we can just hard-code 8. + // int flen = 0; + // if (check_target_feature_is_enabled(str_lit("d"), nullptr)) { + // flen = 8; // Double precision floats are enabled. + // } else if (check_target_feature_is_enabled(str_lit("f"), nullptr)) { + // flen = 4; // Single precision floats are enabled. + // } + int flen = 8; + + LLVMTypeKind kind = LLVMGetTypeKind(type); + i64 size = lb_sizeof(type); + + if (size == 0) { + return lb_arg_type_direct(type, LLVMStructTypeInContext(c, nullptr, 0, false), nullptr, nullptr); + } + + LLVMTypeRef orig_type = type; + + // Flatten down the type so it is easier to check all the ABI conditions. + // Note that we also need to remove all implicit padding fields Odin adds so we keep ABI + // compatibility for struct declarations. + if (kind == LLVMStructTypeKind && size <= gb_max(2*xlen, 2*flen)) { + Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); + flatten(m, &fields, type, false); + + if (fields.count == 1) { + type = fields[0]; + } else { + type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); + } + + kind = LLVMGetTypeKind(type); + size = lb_sizeof(type); + GB_ASSERT_MSG(size == lb_sizeof(orig_type), "flattened: %s of size %d, original: %s of size %d", LLVMPrintTypeToString(type), size, LLVMPrintTypeToString(orig_type), lb_sizeof(orig_type)); + } + + if (is_float(type) && size <= flen && *fprs_left >= 1) { + *fprs_left -= 1; + return non_struct(c, orig_type); + } + + if (kind == LLVMStructTypeKind && size <= 2*flen) { + unsigned elem_count = LLVMCountStructElementTypes(type); + if (elem_count == 2) { + LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(type, 0); + i64 ty1s = lb_sizeof(ty1); + LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(type, 1); + i64 ty2s = lb_sizeof(ty2); + + if (is_float(ty1) && is_float(ty2) && ty1s <= flen && ty2s <= flen && *fprs_left >= 2) { + *fprs_left -= 2; + return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + } + + if (is_float(ty1) && is_register(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { + *fprs_left -= 1; + *gprs_left -= 1; + return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + } + + if (is_register(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { + *fprs_left -= 1; + *gprs_left -= 1; + return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + } + } + } + + // At this point all the cases for floating point registers are exhausted, fit it into + // integer registers or the stack. + // LLVM automatically handles putting args on the stack so we don't check the amount of registers that are left here. + + if (size <= xlen) { + *gprs_left -= 1; + if (is_register(type)) { + return non_struct(c, orig_type); + } else { + return lb_arg_type_direct(orig_type, LLVMIntTypeInContext(c, cast(unsigned)(size*8)), nullptr, nullptr); + } + } else if (size <= 2*xlen) { + LLVMTypeRef *fields = gb_alloc_array(temporary_allocator(), LLVMTypeRef, 2); + fields[0] = LLVMIntTypeInContext(c, cast(unsigned)(xlen*8)); + fields[1] = LLVMIntTypeInContext(c, cast(unsigned)((size-xlen)*8)); + + *gprs_left -= 2; + return lb_arg_type_direct(orig_type, LLVMStructTypeInContext(c, fields, 2, false), nullptr, nullptr); + } else { + return lb_arg_type_indirect(orig_type, nullptr); + } + } + + gb_internal Array compute_arg_types(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *odin_type, int *gprs, int *fprs) { + auto args = array_make(lb_function_type_args_allocator(), arg_count); + + for (unsigned i = 0; i < arg_count; i++) { + LLVMTypeRef type = arg_types[i]; + args[i] = compute_arg_type(m, type, gprs, fprs, odin_type); + } + + return args; + } + + gb_internal lbArgType compute_return_type(lbFunctionType *ft, lbModule *m, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, Type *odin_type, int *agprs) { + LLVMContextRef c = m->ctx; + + if (!return_is_defined) { + return lb_arg_type_direct(LLVMVoidTypeInContext(c)); + } + + // There are two registers for return types. + int gprs = 2; + int fprs = 2; + lbArgType ret = compute_arg_type(m, return_type, &gprs, &fprs, odin_type); + + // Return didn't fit into the return registers, so caller allocates and it is returned via + // an out-pointer. + if (ret.kind == lbArg_Indirect) { + + // Transform multiple return into out pointers if possible. + if (return_is_tuple) { + if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { + int field_count = cast(int)LLVMCountStructElementTypes(return_type); + if (field_count > 1 && field_count <= *agprs) { + ft->original_arg_count = ft->args.count; + ft->multiple_return_original_type = return_type; + + for (int i = 0; i < field_count-1; i++) { + LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(return_type, i); + LLVMTypeRef field_pointer_type = LLVMPointerType(field_type, 0); + lbArgType ret_partial = lb_arg_type_direct(field_pointer_type); + array_add(&ft->args, ret_partial); + *agprs -= 1; + } + GB_ASSERT(*agprs >= 0); + + // override the return type for the last field + LLVMTypeRef new_return_type = LLVMStructGetTypeAtIndex(return_type, field_count-1); + return compute_return_type(ft, m, new_return_type, true, false, odin_type, agprs); + } + } + } + + LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", ret.type); + return lb_arg_type_indirect(ret.type, attr); + } + + return ret; + } + + gb_internal LB_ABI_INFO(abi_info) { + lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); + ft->ctx = m->ctx; + ft->calling_convention = calling_convention; + + int gprs = 8; + int fprs = 8; + + ft->args = compute_arg_types(m, arg_types, arg_count, calling_convention, original_type, &gprs, &fprs); + ft->ret = compute_return_type(ft, m, return_type, return_is_defined, return_is_tuple, original_type, &gprs); + + return ft; + } +} + gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { + LLVMContextRef c = m->ctx; + switch (calling_convention) { case ProcCC_None: case ProcCC_InlineAsm: @@ -1507,33 +1761,35 @@ gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { } case ProcCC_Win64: GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); - return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case ProcCC_SysV: GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); - return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } switch (build_context.metrics.arch) { case TargetArch_amd64: if (build_context.metrics.os == TargetOs_windows) { - return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else if (build_context.metrics.abi == TargetABI_Win64) { - return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else if (build_context.metrics.abi == TargetABI_SysV) { - return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else { - return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } case TargetArch_i386: - return lbAbi386::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbi386::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_arm32: - return lbAbiArm32::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiArm32::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_arm64: - return lbAbiArm64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiArm64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_wasm32: - return lbAbiWasm::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiWasm::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_wasm64p32: - return lbAbiWasm::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiWasm::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + case TargetArch_riscv64: + return lbAbiRiscv64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } GB_PANIC("Unsupported ABI"); @@ -1543,7 +1799,7 @@ gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { gb_internal LB_ABI_INFO(lb_get_abi_info) { lbFunctionType *ft = lb_get_abi_info_internal( - c, + m, arg_types, arg_count, return_type, return_is_defined, ALLOW_SPLIT_MULTI_RETURNS && return_is_tuple && is_calling_convention_odin(calling_convention), @@ -1555,7 +1811,7 @@ gb_internal LB_ABI_INFO(lb_get_abi_info) { // This is to make it consistent when and how it is handled if (calling_convention == ProcCC_Odin) { // append the `context` pointer - lbArgType context_param = lb_arg_type_direct(LLVMPointerType(LLVMInt8TypeInContext(c), 0)); + lbArgType context_param = lb_arg_type_direct(LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0)); array_add(&ft->args, context_param); } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 72ba12516..01ded321e 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -40,7 +40,10 @@ String get_default_microarchitecture() { default_march = str_lit("x86-64-v2"); } } + } else if (build_context.metrics.arch == TargetArch_riscv64) { + default_march = str_lit("generic-rv64"); } + return default_march; } @@ -65,13 +68,33 @@ gb_internal String get_default_features() { } String microarch = get_final_microarchitecture(); + + // NOTE(laytan): for riscv64 to work properly with Odin, we need to enforce some features. + // and we also overwrite the generic target to include more features so we don't default to + // a potato feature set. + if (bc->metrics.arch == TargetArch_riscv64) { + if (microarch == str_lit("generic-rv64")) { + // This is what clang does by default (on -march=rv64gc for General Computing), seems good to also default to. + String features = str_lit("64bit,a,c,d,f,m,relax,zicsr,zifencei"); + + // Update the features string so LLVM uses it later. + if (bc->target_features_string.len > 0) { + bc->target_features_string = concatenate3_strings(permanent_allocator(), features, str_lit(","), bc->target_features_string); + } else { + bc->target_features_string = features; + } + + return features; + } + } + for (int i = off; i < off+target_microarch_counts[bc->metrics.arch]; i += 1) { if (microarch_features_list[i].microarch == microarch) { return microarch_features_list[i].features; } } - GB_PANIC("unknown microarch"); + GB_PANIC("unknown microarch: %.*s", LIT(microarch)); return {}; } @@ -3030,6 +3053,12 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { // Always use PIC for OpenBSD and Haiku: they default to PIE reloc_mode = LLVMRelocPIC; } + + if (build_context.metrics.arch == TargetArch_riscv64) { + // NOTE(laytan): didn't seem to work without this. + reloc_mode = LLVMRelocPIC; + } + break; case RelocMode_Static: reloc_mode = LLVMRelocStatic; @@ -3052,6 +3081,13 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbModule *m = entry.value; m->target_machine = target_machine; LLVMSetModuleDataLayout(m->mod, LLVMCreateTargetDataLayout(target_machine)); + + #if LLVM_VERSION_MAJOR >= 18 + if (build_context.fast_isel) { + LLVMSetTargetMachineFastISel(m->target_machine, true); + } + #endif + array_add(&target_machines, target_machine); } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 02daecf6b..29d2ccfe6 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -137,6 +137,12 @@ enum lbFunctionPassManagerKind { lbFunctionPassManager_COUNT }; +struct lbPadType { + i64 padding; + i64 padding_align; + LLVMTypeRef type; +}; + struct lbModule { LLVMModuleRef mod; LLVMContextRef ctx; @@ -199,6 +205,9 @@ struct lbModule { PtrMap exact_value_compound_literal_addr_map; // Key: Ast_CompoundLit LLVMPassManagerRef function_pass_managers[lbFunctionPassManager_COUNT]; + + BlockingMutex pad_types_mutex; + Array pad_types; }; struct lbEntityCorrection { diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index c896f889d..68e1efc1c 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -55,6 +55,16 @@ gb_internal void lb_debug_file_line(lbModule *m, Ast *node, LLVMMetadataRef *fil } } +gb_internal LLVMMetadataRef lb_debug_procedure_parameters(lbModule *m, Type *type) { + if (is_type_proc(type)) { + return lb_debug_type(m, t_rawptr); + } + if (type->kind == Type_Tuple && type->Tuple.variables.count == 1) { + return lb_debug_procedure_parameters(m, type->Tuple.variables[0]->type); + } + return lb_debug_type(m, type); +} + gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) { i64 size = type_size_of(type); // Check size gb_unused(size); @@ -72,13 +82,36 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) parameter_count += 1; } } - LLVMMetadataRef *parameters = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, parameter_count); - unsigned param_index = 0; - if (type->Proc.result_count == 0) { - parameters[param_index++] = nullptr; - } else { - parameters[param_index++] = lb_debug_type(m, type->Proc.results); + auto parameters = array_make(permanent_allocator(), 0, type->Proc.param_count+type->Proc.result_count+2); + + array_add(¶meters, cast(LLVMMetadataRef)nullptr); + + bool return_is_tuple = false; + if (type->Proc.result_count != 0) { + Type *single_ret = reduce_tuple_to_single_type(type->Proc.results); + if (is_type_proc(single_ret)) { + single_ret = t_rawptr; + } + if (is_type_tuple(single_ret) && is_calling_convention_odin(type->Proc.calling_convention)) { + LLVMTypeRef actual = lb_type_internal_for_procedures_raw(m, type); + actual = LLVMGetReturnType(actual); + if (actual == nullptr) { + // results were passed as a single pointer + parameters[0] = lb_debug_procedure_parameters(m, single_ret); + } else { + LLVMTypeRef possible = lb_type(m, type->Proc.results); + if (possible == actual) { + // results were returned directly + parameters[0] = lb_debug_procedure_parameters(m, single_ret); + } else { + // resulsts were returned separately + return_is_tuple = true; + } + } + } else { + parameters[0] = lb_debug_procedure_parameters(m, single_ret); + } } LLVMMetadataRef file = nullptr; @@ -88,8 +121,22 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) if (e->kind != Entity_Variable) { continue; } - parameters[param_index] = lb_debug_type(m, e->type); - param_index += 1; + array_add(¶meters, lb_debug_procedure_parameters(m, e->type)); + } + + + if (return_is_tuple) { + Type *results = type->Proc.results; + GB_ASSERT(results != nullptr && results->kind == Type_Tuple); + isize count = results->Tuple.variables.count; + parameters[0] = lb_debug_procedure_parameters(m, results->Tuple.variables[count-1]->type); + for (isize i = 0; i < count-1; i++) { + array_add(¶meters, lb_debug_procedure_parameters(m, results->Tuple.variables[i]->type)); + } + } + + if (type->Proc.calling_convention == ProcCC_Odin) { + array_add(¶meters, lb_debug_type(m, t_context_ptr)); } LLVMDIFlags flags = LLVMDIFlagZero; @@ -97,7 +144,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) flags = LLVMDIFlagNoReturn; } - return LLVMDIBuilderCreateSubroutineType(m->debug_builder, file, parameters, parameter_count, flags); + return LLVMDIBuilderCreateSubroutineType(m->debug_builder, file, parameters.data, cast(unsigned)parameters.count, flags); } gb_internal LLVMMetadataRef lb_debug_struct_field(lbModule *m, String const &name, Type *type, u64 offset_in_bits) { @@ -969,7 +1016,7 @@ gb_internal LLVMMetadataRef lb_debug_type(lbModule *m, Type *type) { return lb_debug_struct(m, type, bt, name, scope, file, line); } - case Type_Struct: return lb_debug_struct(m, type, base_type(type), name, scope, file, line); + case Type_Struct: return lb_debug_struct(m, type, bt, name, scope, file, line); case Type_Slice: return lb_debug_slice(m, type, name, scope, file, line); case Type_DynamicArray: return lb_debug_dynamic_array(m, type, name, scope, file, line); case Type_Union: return lb_debug_union(m, type, name, scope, file, line); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 1f0719e13..f20c52e88 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -136,6 +136,11 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, switch (op) { case Token_Xor: opv = LLVMBuildNot(p->builder, v, ""); + if (is_type_bit_set(elem_type)) { + ExactValue ev_mask = exact_bit_set_all_set_mask(elem_type); + lbValue mask = lb_const_value(p->module, elem_type, ev_mask); + opv = LLVMBuildAnd(p->builder, opv, mask.value, ""); + } break; case Token_Sub: if (is_type_float(elem_type)) { @@ -176,8 +181,14 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, if (op == Token_Xor) { lbValue cmp = {}; - cmp.value = LLVMBuildNot(p->builder, x.value, ""); cmp.type = x.type; + if (is_type_bit_set(x.type)) { + ExactValue ev_mask = exact_bit_set_all_set_mask(x.type); + lbValue mask = lb_const_value(p->module, x.type, ev_mask); + cmp.value = LLVMBuildXor(p->builder, x.value, mask.value, ""); + } else { + cmp.value = LLVMBuildNot(p->builder, x.value, ""); + } return lb_emit_conv(p, cmp, type); } @@ -694,31 +705,37 @@ gb_internal lbValue lb_emit_matrix_flatten(lbProcedure *p, lbValue m, Type *type lbAddr res = lb_add_local_generated(p, type, true); - i64 row_count = mt->Matrix.row_count; - i64 column_count = mt->Matrix.column_count; - TEMPORARY_ALLOCATOR_GUARD(); + GB_ASSERT(type_size_of(type) == type_size_of(m.type)); - auto srcs = array_make(temporary_allocator(), 0, row_count*column_count); - auto dsts = array_make(temporary_allocator(), 0, row_count*column_count); + lbValue m_ptr = lb_address_from_load_or_generate_local(p, m); + lbValue n = lb_const_int(p->module, t_int, type_size_of(type)); + lb_mem_copy_non_overlapping(p, res.addr, m_ptr, n); - for (i64 j = 0; j < column_count; j++) { - for (i64 i = 0; i < row_count; i++) { - lbValue src = lb_emit_matrix_ev(p, m, i, j); - array_add(&srcs, src); - } - } + // i64 row_count = mt->Matrix.row_count; + // i64 column_count = mt->Matrix.column_count; + // TEMPORARY_ALLOCATOR_GUARD(); - for (i64 j = 0; j < column_count; j++) { - for (i64 i = 0; i < row_count; i++) { - lbValue dst = lb_emit_array_epi(p, res.addr, i + j*row_count); - array_add(&dsts, dst); - } - } + // auto srcs = array_make(temporary_allocator(), 0, row_count*column_count); + // auto dsts = array_make(temporary_allocator(), 0, row_count*column_count); - GB_ASSERT(srcs.count == dsts.count); - for_array(i, srcs) { - lb_emit_store(p, dsts[i], srcs[i]); - } + // for (i64 j = 0; j < column_count; j++) { + // for (i64 i = 0; i < row_count; i++) { + // lbValue src = lb_emit_matrix_ev(p, m, i, j); + // array_add(&srcs, src); + // } + // } + + // for (i64 j = 0; j < column_count; j++) { + // for (i64 i = 0; i < row_count; i++) { + // lbValue dst = lb_emit_array_epi(p, res.addr, i + j*row_count); + // array_add(&dsts, dst); + // } + // } + + // GB_ASSERT(srcs.count == dsts.count); + // for_array(i, srcs) { + // lb_emit_store(p, dsts[i], srcs[i]); + // } return lb_addr_load(p, res); } @@ -2028,7 +2045,11 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } else if (is_type_integer(src_elem) && is_type_boolean(dst_elem)) { LLVMValueRef i1vector = LLVMBuildICmp(p->builder, LLVMIntNE, value.value, LLVMConstNull(LLVMTypeOf(value.value)), ""); res.value = LLVMBuildIntCast2(p->builder, i1vector, lb_type(m, t), !is_type_unsigned(src_elem), ""); - } else { + } else if (is_type_pointer(src_elem) && is_type_integer(dst_elem)) { + res.value = LLVMBuildPtrToInt(p->builder, value.value, lb_type(m, t), ""); + } else if (is_type_integer(src_elem) && is_type_pointer(dst_elem)) { + res.value = LLVMBuildIntToPtr(p->builder, value.value, lb_type(m, t), ""); + }else { GB_PANIC("Unhandled simd vector conversion: %s -> %s", type_to_string(src), type_to_string(dst)); } return res; @@ -2465,6 +2486,17 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return {}; } +gb_internal lbValue lb_emit_c_vararg(lbProcedure *p, lbValue arg, Type *type) { + Type *core = core_type(type); + if (core->kind == Type_BitSet) { + core = core_type(bit_set_to_int(core)); + arg = lb_emit_transmute(p, arg, core); + } + + Type *promoted = c_vararg_promote_type(core); + return lb_emit_conv(p, arg, promoted); +} + gb_internal lbValue lb_compare_records(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right, Type *type) { GB_ASSERT((is_type_struct(type) || is_type_union(type)) && is_type_comparable(type)); lbValue left_ptr = lb_address_from_load_or_generate_local(p, left); @@ -3125,15 +3157,6 @@ gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, return {}; } -gb_internal lbValue lb_make_soa_pointer(lbProcedure *p, Type *type, lbValue const &addr, lbValue const &index) { - lbAddr v = lb_add_local_generated(p, type, false); - lbValue ptr = lb_emit_struct_ep(p, v.addr, 0); - lbValue idx = lb_emit_struct_ep(p, v.addr, 1); - lb_emit_store(p, ptr, addr); - lb_emit_store(p, idx, lb_emit_conv(p, index, t_int)); - - return lb_addr_load(p, v); -} gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { ast_node(ue, UnaryExpr, expr); @@ -3739,7 +3762,9 @@ gb_internal lbValue lb_get_using_variable(lbProcedure *p, Entity *e) { lbValue v = {}; + bool is_soa = false; if (pv == nullptr && parent->flags & EntityFlag_SoaPtrField) { + is_soa = true; // NOTE(bill): using SOA value (probably from for-in statement) lbAddr parent_addr = lb_get_soa_variable_addr(p, parent); v = lb_addr_get_ptr(p, parent_addr); @@ -3750,7 +3775,7 @@ gb_internal lbValue lb_get_using_variable(lbProcedure *p, Entity *e) { v = lb_build_addr_ptr(p, e->using_expr); } GB_ASSERT(v.value != nullptr); - GB_ASSERT_MSG(parent->type == type_deref(v.type), "%s %s", type_to_string(parent->type), type_to_string(v.type)); + GB_ASSERT_MSG(is_soa || parent->type == type_deref(v.type), "%s %s", type_to_string(parent->type), type_to_string(v.type)); lbValue ptr = lb_emit_deep_field_gep(p, v, sel); if (parent->scope) { if ((parent->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) { @@ -3814,7 +3839,7 @@ gb_internal lbAddr lb_build_array_swizzle_addr(lbProcedure *p, AstCallExpr *ce, Type *type = base_type(lb_addr_type(addr)); GB_ASSERT(type->kind == Type_Array); i64 count = type->Array.count; - if (count <= 4) { + if (count <= 4 && index_count <= 4) { u8 indices[4] = {}; u8 index_count = 0; for (i32 i = 1; i < ce->args.count; i++) { @@ -5101,8 +5126,9 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { a = lb_addr_get_ptr(p, addr); } - GB_ASSERT(is_type_array(expr->tav.type) || is_type_simd_vector(expr->tav.type)); - return lb_addr_swizzle(a, expr->tav.type, swizzle_count, swizzle_indices); + Type *type = type_deref(expr->tav.type); + GB_ASSERT(is_type_array(type) || is_type_simd_vector(type)); + return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices); } Selection sel = lookup_field(type, selector, false); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index a91c1d1fe..842a1cbc8 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -91,6 +91,9 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { map_init(&m->map_cell_info_map, 0); map_init(&m->exact_value_compound_literal_addr_map, 1024); + array_init(&m->pad_types, heap_allocator()); + + m->const_dummy_builder = LLVMCreateBuilderInContext(m->ctx); } @@ -534,6 +537,15 @@ gb_internal lbValue lb_relative_pointer_to_pointer(lbProcedure *p, lbAddr const return final_ptr; } +gb_internal lbValue lb_make_soa_pointer(lbProcedure *p, Type *type, lbValue const &addr, lbValue const &index) { + lbAddr v = lb_add_local_generated(p, type, false); + lbValue ptr = lb_emit_struct_ep(p, v.addr, 0); + lbValue idx = lb_emit_struct_ep(p, v.addr, 1); + lb_emit_store(p, ptr, addr); + lb_emit_store(p, idx, lb_emit_conv(p, index, t_int)); + + return lb_addr_load(p, v); +} gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { if (addr.addr.value == nullptr) { @@ -549,8 +561,12 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { return lb_relative_pointer_to_pointer(p, addr); case lbAddr_SoaVariable: - // TODO(bill): FIX THIS HACK - return lb_address_from_load(p, lb_addr_load(p, addr)); + { + Type *soa_ptr_type = alloc_type_soa_pointer(lb_addr_type(addr)); + return lb_address_from_load_or_generate_local(p, lb_make_soa_pointer(p, soa_ptr_type, addr.addr, addr.soa.index)); + // TODO(bill): FIX THIS HACK + // return lb_address_from_load(p, lb_addr_load(p, addr)); + } case lbAddr_Context: GB_PANIC("lbAddr_Context should be handled elsewhere"); @@ -1771,7 +1787,7 @@ gb_internal LLVMTypeRef lb_type_internal_for_procedures_raw(lbModule *m, Type *t } } GB_ASSERT(param_index == param_count); - lbFunctionType *ft = lb_get_abi_info(m->ctx, params, param_count, ret, ret != nullptr, return_is_tuple, type->Proc.calling_convention, type); + lbFunctionType *ft = lb_get_abi_info(m, params, param_count, ret, ret != nullptr, return_is_tuple, type->Proc.calling_convention, type); { for_array(j, ft->args) { auto arg = ft->args[j]; @@ -2098,6 +2114,12 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { llvm_type = LLVMStructCreateNamed(ctx, name); map_set(&m->types, type, llvm_type); lb_clone_struct_type(llvm_type, lb_type(m, base)); + + if (base->kind == Type_Struct) { + map_set(&m->struct_field_remapping, cast(void *)llvm_type, lb_get_struct_remapping(m, base)); + map_set(&m->struct_field_remapping, cast(void *)type, lb_get_struct_remapping(m, base)); + } + return llvm_type; } } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 2f736ff6c..e850d3364 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1527,6 +1527,23 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } + case BuiltinProc_simd_reduce_any: + case BuiltinProc_simd_reduce_all: + { + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_reduce_any: name = "llvm.vector.reduce.or"; break; + case BuiltinProc_simd_reduce_all: name = "llvm.vector.reduce.and"; break; + } + + LLVMTypeRef types[1] = { lb_type(p->module, arg0.type) }; + LLVMValueRef args[1] = { arg0.value }; + + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + return res; + } + + case BuiltinProc_simd_shuffle: { Type *vt = arg0.type; @@ -1629,13 +1646,13 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn } - case BuiltinProc_simd_add_sat: - case BuiltinProc_simd_sub_sat: + case BuiltinProc_simd_saturating_add: + case BuiltinProc_simd_saturating_sub: { char const *name = nullptr; switch (builtin_id) { - case BuiltinProc_simd_add_sat: name = is_signed ? "llvm.sadd.sat" : "llvm.uadd.sat"; break; - case BuiltinProc_simd_sub_sat: name = is_signed ? "llvm.ssub.sat" : "llvm.usub.sat"; break; + case BuiltinProc_simd_saturating_add: name = is_signed ? "llvm.sadd.sat" : "llvm.uadd.sat"; break; + case BuiltinProc_simd_saturating_sub: name = is_signed ? "llvm.ssub.sat" : "llvm.usub.sat"; break; } LLVMTypeRef types[1] = {lb_type(p->module, arg0.type)}; @@ -1671,6 +1688,85 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } + + case BuiltinProc_simd_gather: + case BuiltinProc_simd_scatter: + case BuiltinProc_simd_masked_load: + case BuiltinProc_simd_masked_store: + case BuiltinProc_simd_masked_expand_load: + case BuiltinProc_simd_masked_compress_store: + { + LLVMValueRef ptr = arg0.value; + LLVMValueRef val = arg1.value; + LLVMValueRef mask = arg2.value; + + unsigned count = cast(unsigned)get_array_type_count(arg1.type); + + LLVMTypeRef mask_type = LLVMVectorType(LLVMInt1TypeInContext(p->module->ctx), count); + mask = LLVMBuildTrunc(p->builder, mask, mask_type, ""); + + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_gather: name = "llvm.masked.gather"; break; + case BuiltinProc_simd_scatter: name = "llvm.masked.scatter"; break; + case BuiltinProc_simd_masked_load: name = "llvm.masked.load"; break; + case BuiltinProc_simd_masked_store: name = "llvm.masked.store"; break; + case BuiltinProc_simd_masked_expand_load: name = "llvm.masked.expandload"; break; + case BuiltinProc_simd_masked_compress_store: name = "llvm.masked.compressstore"; break; + } + unsigned type_count = 2; + LLVMTypeRef types[2] = { + lb_type(p->module, arg1.type), + lb_type(p->module, arg0.type) + }; + + auto alignment = cast(unsigned long long)type_align_of(base_array_type(arg1.type)); + LLVMValueRef align = LLVMConstInt(LLVMInt32TypeInContext(p->module->ctx), alignment, false); + + unsigned arg_count = 4; + LLVMValueRef args[4] = {}; + switch (builtin_id) { + case BuiltinProc_simd_masked_load: + types[1] = lb_type(p->module, t_rawptr); + /*fallthrough*/ + case BuiltinProc_simd_gather: + args[0] = ptr; + args[1] = align; + args[2] = mask; + args[3] = val; + break; + + case BuiltinProc_simd_masked_store: + types[1] = lb_type(p->module, t_rawptr); + /*fallthrough*/ + case BuiltinProc_simd_scatter: + args[0] = val; + args[1] = ptr; + args[2] = align; + args[3] = mask; + break; + + case BuiltinProc_simd_masked_expand_load: + arg_count = 3; + type_count = 1; + args[0] = ptr; + args[1] = mask; + args[2] = val; + break; + + case BuiltinProc_simd_masked_compress_store: + arg_count = 3; + type_count = 1; + args[0] = val; + args[1] = ptr; + args[2] = mask; + break; + } + + res.value = lb_call_intrinsic(p, name, args, arg_count, types, type_count); + return res; + + } } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); @@ -2285,8 +2381,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return res; } - case BuiltinProc_add_sat: - case BuiltinProc_sub_sat: + case BuiltinProc_saturating_add: + case BuiltinProc_saturating_sub: { Type *main_type = tv.type; Type *type = main_type; @@ -2299,13 +2395,13 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu 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; + case BuiltinProc_saturating_add: name = "llvm.uadd.sat"; break; + case BuiltinProc_saturating_sub: 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; + case BuiltinProc_saturating_add: name = "llvm.sadd.sat"; break; + case BuiltinProc_saturating_sub: name = "llvm.ssub.sat"; break; } } LLVMTypeRef types[1] = {lb_type(p->module, type)}; @@ -2781,6 +2877,31 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu LLVMValueRef inline_asm = nullptr; switch (build_context.metrics.arch) { + case TargetArch_riscv64: + { + GB_ASSERT(arg_count <= 7); + + char asm_string[] = "ecall"; + gbString constraints = gb_string_make(heap_allocator(), "={a0}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "a7", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6" + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } + break; case TargetArch_amd64: { GB_ASSERT(arg_count <= 7); @@ -3473,9 +3594,9 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { if (is_type_untyped_nil(arg.type)) { arg = lb_const_nil(p->module, t_rawptr); } - array_add(&args, lb_emit_conv(p, arg, c_vararg_promote_type(default_type(arg.type)))); + array_add(&args, lb_emit_c_vararg(p, arg, arg.type)); } else { - array_add(&args, lb_emit_conv(p, arg, c_vararg_promote_type(elem_type))); + array_add(&args, lb_emit_c_vararg(p, arg, elem_type)); } } break; @@ -3601,15 +3722,15 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { if (is_type_untyped_nil(arg.type)) { arg = lb_const_nil(p->module, t_rawptr); } - array_add(&args, lb_emit_conv(p, arg, c_vararg_promote_type(default_type(arg.type)))); + array_add(&args, lb_emit_c_vararg(p, arg, arg.type)); } else { - array_add(&args, lb_emit_conv(p, arg, c_vararg_promote_type(elem_type))); + array_add(&args, lb_emit_c_vararg(p, arg, elem_type)); } } } else { lbValue value = lb_build_expr(p, fv->value); GB_ASSERT(!is_type_tuple(value.type)); - array_add(&args, lb_emit_conv(p, value, c_vararg_promote_type(value.type))); + array_add(&args, lb_emit_c_vararg(p, value, value.type)); } } else { lbValue value = lb_build_expr(p, fv->value); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index e70cc503e..df3d4bc03 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1404,6 +1404,10 @@ gb_internal bool lb_switch_stmt_can_be_trivial_jump_table(AstSwitchStmt *ss, boo } + if (is_typeid) { + return false; + } + return true; } @@ -1574,9 +1578,14 @@ gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValu GB_ASSERT(e != nullptr); if (e->flags & EntityFlag_Value) { // by value - GB_ASSERT(are_types_identical(e->type, value.type)); - lbAddr x = lb_add_local(p, e->type, e, false); - lb_addr_store(p, x, value); + if (are_types_identical(e->type, value.type)) { + lbAddr x = lb_add_local(p, e->type, e, false); + lb_addr_store(p, x, value); + } else { + GB_ASSERT_MSG(are_types_identical(e->type, type_deref(value.type)), "%s", type_to_string(value.type)); + lbAddr x = lb_add_local(p, e->type, e, false); + lb_addr_store(p, x, lb_emit_load(p, value)); + } } else { if (!is_default_case) { Type *clause_type = e->type; @@ -1646,7 +1655,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss union_data = lb_emit_conv(p, parent_ptr, t_rawptr); Type *union_type = type_deref(parent_ptr.type); if (is_type_union_maybe_pointer(union_type)) { - tag = lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_NotEq, union_data), t_int); + tag = lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_NotEq, parent_value), t_int); } else if (union_tag_size(union_type) == 0) { tag = {}; // there is no tag for a zero sized union } else { @@ -2190,8 +2199,7 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { // and `LLVMConstIntGetZExtValue()` calls below will be valid and `LLVMInstructionEraseFromParent()` // will target the correct (& only) branch statement - - if (cond.value && LLVMIsConstant(cond.value)) { + if (cond.value && LLVMIsAConstantInt(cond.value)) { // NOTE(bill): Do a compile time short circuit for when the condition is constantly known. // This done manually rather than relying on the SSA passes because sometimes the SSA passes // miss some even if they are constantly known, especially with few optimization passes. diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 1165476be..f63c42ab9 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -263,7 +263,7 @@ gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) { if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), ""); return res; - } else if (is_type_array_like(src) && is_type_simd_vector(dst)) { + } else if (is_type_array_like(src) && (is_type_simd_vector(dst) || is_type_integer_128bit(dst))) { unsigned align = cast(unsigned)gb_max(type_align_of(src), type_align_of(dst)); lbValue ptr = lb_address_from_load_or_generate_local(p, value); if (lb_try_update_alignment(ptr, align)) { @@ -1003,6 +1003,21 @@ gb_internal i32 lb_convert_struct_index(lbModule *m, Type *t, i32 index) { } gb_internal LLVMTypeRef lb_type_padding_filler(lbModule *m, i64 padding, i64 padding_align) { + MUTEX_GUARD(&m->pad_types_mutex); + if (padding % padding_align == 0) { + for (auto pd : m->pad_types) { + if (pd.padding == padding && pd.padding_align == padding_align) { + return pd.type; + } + } + } else { + for (auto pd : m->pad_types) { + if (pd.padding == padding && pd.padding_align == 1) { + return pd.type; + } + } + } + // NOTE(bill): limit to `[N x u64]` to prevent ABI issues padding_align = gb_clamp(padding_align, 1, 8); if (padding % padding_align == 0) { @@ -1016,13 +1031,19 @@ gb_internal LLVMTypeRef lb_type_padding_filler(lbModule *m, i64 padding, i64 pad } GB_ASSERT_MSG(elem != nullptr, "Invalid lb_type_padding_filler padding and padding_align: %lld", padding_align); + + LLVMTypeRef type = nullptr; if (len != 1) { - return llvm_array_type(elem, len); + type = llvm_array_type(elem, len); } else { - return elem; + type = elem; } + array_add(&m->pad_types, lbPadType{padding, padding_align, type}); + return type; } else { - return llvm_array_type(lb_type(m, t_u8), padding); + LLVMTypeRef type = llvm_array_type(lb_type(m, t_u8), padding); + array_add(&m->pad_types, lbPadType{padding, 1, type}); + return type; } } diff --git a/src/main.cpp b/src/main.cpp index 41a95338b..0a84b2f97 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -389,6 +389,7 @@ enum BuildFlagKind { BuildFlag_PrintLinkerFlags, // internal use only + BuildFlag_InternalFastISel, BuildFlag_InternalIgnoreLazy, BuildFlag_InternalIgnoreLLVMBuild, BuildFlag_InternalIgnorePanic, @@ -594,6 +595,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_PrintLinkerFlags, str_lit("print-linker-flags"), BuildFlagParam_None, Command_build); + add_flag(&build_flags, BuildFlag_InternalFastISel, str_lit("internal-fast-isel"), BuildFlagParam_None, Command_all); 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); @@ -1408,6 +1410,9 @@ gb_internal bool parse_build_flags(Array args) { build_context.print_linker_flags = true; break; + case BuildFlag_InternalFastISel: + build_context.fast_isel = true; + break; case BuildFlag_InternalIgnoreLazy: build_context.ignore_lazy = true; break; @@ -2268,6 +2273,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Sets the build mode."); print_usage_line(2, "Available options:"); print_usage_line(3, "-build-mode:exe Builds as an executable."); + print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); 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."); @@ -2973,13 +2979,19 @@ int main(int arg_count, char const **arg_ptr) { break; } } - if(run_args_start_idx != -1) { + if (run_args_start_idx != -1) { last_non_run_arg = run_args_start_idx; + + if (run_args_start_idx == 2) { + // missing src path on argv[2], invocation: odin [run|test] -- + usage(args[0]); + return 1; + } + for(isize i = run_args_start_idx+1; i < args.count; ++i) { array_add(&run_args, args[i]); } } - args = array_slice(args, 0, last_non_run_arg); run_args_string = string_join_and_quote(heap_allocator(), run_args); @@ -3239,6 +3251,21 @@ int main(int arg_count, char const **arg_ptr) { } } + // NOTE(laytan): on riscv64 we want to enforce some features. + if (build_context.metrics.arch == TargetArch_riscv64) { + String disabled; + if (!check_target_feature_is_enabled(str_lit("64bit,f,d,m"), &disabled)) { // 64bit, floats, doubles, integer multiplication. + gb_printf_err("missing required target feature: \"%.*s\", enable it by setting a different -microarch or explicitly adding it through -target-features\n", LIT(disabled)); + gb_exit(1); + } + + // NOTE(laytan): some weird errors on LLVM 14 that LLVM 17 fixes. + if (LLVM_VERSION_MAJOR < 17) { + gb_printf_err("Invalid LLVM version %s, RISC-V targets require at least LLVM 17\n", LLVM_VERSION_STRING); + gb_exit(1); + } + } + if (build_context.show_debug_messages) { debugf("Selected microarch: %.*s\n", LIT(march)); debugf("Default microarch features: %.*s\n", LIT(default_features)); diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index 899583143..b0fd22a23 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -684,8 +684,14 @@ gb_internal void find_visual_studio_paths_from_env_vars(Find_Result *result) { ? str_lit("lib\\x64\\") : str_lit("lib\\x86\\"); - result->vs_exe_path = mc_concat(vctid, exe); - result->vs_library_path = mc_concat(vctid, lib); + if (string_ends_with(vctid, str_lit("\\"))) { + result->vs_exe_path = mc_concat(vctid, exe); + result->vs_library_path = mc_concat(vctid, lib); + } else { + result->vs_exe_path = mc_concat(vctid, str_lit("\\"), exe); + result->vs_library_path = mc_concat(vctid, str_lit("\\"), lib); + } + vs_found = true; } diff --git a/src/parser.cpp b/src/parser.cpp index ba67d4594..4fea85a77 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1921,6 +1921,9 @@ gb_internal Array parse_enum_field_list(AstFile *f) { f->curr_token.kind != Token_EOF) { CommentGroup *docs = f->lead_comment; CommentGroup *comment = nullptr; + + parse_enforce_tabs(f); + Ast *name = parse_value(f); Ast *value = nullptr; if (f->curr_token.kind == Token_Eq) { @@ -2259,6 +2262,7 @@ gb_internal Array parse_union_variant_list(AstFile *f) { auto variants = array_make(ast_allocator(f)); while (f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { + parse_enforce_tabs(f); Ast *type = parse_type(f); if (type->kind != Ast_BadExpr) { array_add(&variants, type); @@ -3560,7 +3564,12 @@ gb_internal Ast *parse_type(AstFile *f) { } else { token = advance_token(f); } - syntax_error(token, "Expected a type, got '%.*s'", LIT(prev_token.string)); + String prev_token_str = prev_token.string; + if (prev_token_str == str_lit("\n")) { + syntax_error(token, "Expected a type, got newline"); + } else { + syntax_error(token, "Expected a type, got '%.*s'", LIT(prev_token_str)); + } return ast_bad_expr(f, token, f->curr_token); } else if (type->kind == Ast_ParenExpr && unparen_expr(type) == nullptr) { @@ -4269,6 +4278,7 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl while (f->curr_token.kind != follow && f->curr_token.kind != Token_Colon && f->curr_token.kind != Token_EOF) { + if (!is_signature) parse_enforce_tabs(f); u32 flags = parse_field_prefixes(f); Ast *param = parse_var_type(f, allow_ellipsis, allow_typeid_token); if (param->kind == Ast_Ellipsis) { @@ -4358,6 +4368,8 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl f->curr_token.kind != Token_EOF && f->curr_token.kind != Token_Semicolon) { CommentGroup *docs = f->lead_comment; + + if (!is_signature) parse_enforce_tabs(f); u32 set_flags = parse_field_prefixes(f); Token tag = {}; Array names = parse_ident_list(f, allow_poly_names); @@ -4527,6 +4539,10 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { return ast_bad_stmt(f, f->curr_token, f->curr_token); } + Ast *top_if_stmt = nullptr; + + Ast *prev_if_stmt = nullptr; +if_else_chain:; Token token = expect_token(f, Token_if); Ast *init = nullptr; Ast *cond = nullptr; @@ -4568,12 +4584,24 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { ignore_strict_style = true; } skip_possible_newline_for_literal(f, ignore_strict_style); + + Ast *curr_if_stmt = ast_if_stmt(f, token, init, cond, body, nullptr); + if (top_if_stmt == nullptr) { + top_if_stmt = curr_if_stmt; + } + if (prev_if_stmt != nullptr) { + prev_if_stmt->IfStmt.else_stmt = curr_if_stmt; + } + if (f->curr_token.kind == Token_else) { Token else_token = expect_token(f, Token_else); switch (f->curr_token.kind) { case Token_if: - else_stmt = parse_if_stmt(f); - break; + // NOTE(bill): Instead of relying on recursive descent for an if-else chain + // we can just inline the tail-recursion manually with a simple loop like + // construct using a `goto` + prev_if_stmt = curr_if_stmt; + goto if_else_chain; case Token_OpenBrace: else_stmt = parse_block_stmt(f, false); break; @@ -4588,7 +4616,9 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { } } - return ast_if_stmt(f, token, init, cond, body, else_stmt); + curr_if_stmt->IfStmt.else_stmt = else_stmt; + + return top_if_stmt; } gb_internal Ast *parse_when_stmt(AstFile *f) { @@ -5352,6 +5382,11 @@ gb_internal u64 check_vet_flags(AstFile *file) { gb_internal void parse_enforce_tabs(AstFile *f) { + // Checks to see if tabs have been used for indentation + if ((check_vet_flags(f) & VetFlag_Tabs) == 0) { + return; + } + Token prev = f->prev_token; Token curr = f->curr_token; if (prev.pos.line < curr.pos.line) { @@ -5368,6 +5403,10 @@ gb_internal void parse_enforce_tabs(AstFile *f) { isize len = end-it; for (isize i = 0; i < len; i++) { + if (it[i] == '/') { + // ignore comments + break; + } if (it[i] == ' ') { syntax_error(curr, "With '-vet-tabs', tabs must be used for indentation"); break; @@ -5382,11 +5421,7 @@ gb_internal Array parse_stmt_list(AstFile *f) { while (f->curr_token.kind != Token_case && f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { - - // Checks to see if tabs have been used for indentation - if (check_vet_flags(f) & VetFlag_Tabs) { - parse_enforce_tabs(f); - } + parse_enforce_tabs(f); Ast *stmt = parse_stmt(f); if (stmt && stmt->kind != Ast_EmptyStmt) { @@ -6448,7 +6483,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } f->total_file_decl_count += calc_decl_count(stmt); - if (stmt->kind == Ast_WhenStmt || stmt->kind == Ast_ExprStmt || stmt->kind == Ast_ImportDecl) { + if (stmt->kind == Ast_WhenStmt || stmt->kind == Ast_ExprStmt || stmt->kind == Ast_ImportDecl || stmt->kind == Ast_ForeignBlockDecl) { f->delayed_decl_count += 1; } } diff --git a/src/parser.hpp b/src/parser.hpp index 565a8e621..d5117a31e 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -82,6 +82,7 @@ enum AstFileFlag : u32 { enum AstDelayQueueKind { AstDelayQueue_Import, AstDelayQueue_Expr, + AstDelayQueue_ForeignBlock, AstDelayQueue_COUNT, }; @@ -885,4 +886,7 @@ gb_internal gb_inline gbAllocator ast_allocator(AstFile *f) { gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind); gb_internal gbString expr_to_string(Ast *expression); -gb_internal bool allow_field_separator(AstFile *f); \ No newline at end of file +gb_internal bool allow_field_separator(AstFile *f); + + +gb_internal void parse_enforce_tabs(AstFile *f); \ No newline at end of file diff --git a/src/threading.cpp b/src/threading.cpp index 011b66028..af8fd803c 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -529,6 +529,9 @@ gb_internal gb_inline void yield_thread(void) { _mm_pause(); #elif defined(GB_CPU_ARM) __asm__ volatile ("yield" : : : "memory"); +#elif defined(GB_CPU_RISCV) + // I guess? + __asm__ volatile ("nop" : : : "memory"); #else #error Unknown architecture #endif @@ -839,8 +842,16 @@ gb_internal void futex_signal(Futex *f) { } } else { #endif + // UL_COMPARE_AND_WAIT_SHARED is only available on macOS 10.15+ + int WAIT_FLAG; + if (__builtin_available(macOS 10.15, *)) { + WAIT_FLAG = UL_COMPARE_AND_WAIT_SHARED; + } else { + WAIT_FLAG = UL_COMPARE_AND_WAIT; + } + for (;;) { - int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, 0); + int ret = __ulock_wake(WAIT_FLAG | ULF_NO_ERRNO, f, 0); if (ret >= 0) { return; } @@ -875,9 +886,17 @@ gb_internal void futex_broadcast(Futex *f) { } } else { #endif + // UL_COMPARE_AND_WAIT_SHARED is only available on macOS 10.15+ + int WAIT_FLAG; + if (__builtin_available(macOS 10.15, *)) { + WAIT_FLAG = UL_COMPARE_AND_WAIT_SHARED; + } else { + WAIT_FLAG = UL_COMPARE_AND_WAIT; + } + for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; - int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); + int ret = __ulock_wake(WAIT_FLAG | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); if (ret == 0) { return; } @@ -915,8 +934,16 @@ gb_internal void futex_wait(Futex *f, Footex val) { } } else { #endif + // UL_COMPARE_AND_WAIT_SHARED is only available on macOS 10.15+ + int WAIT_FLAG; + if (__builtin_available(macOS 10.15, *)) { + WAIT_FLAG = UL_COMPARE_AND_WAIT_SHARED; + } else { + WAIT_FLAG = UL_COMPARE_AND_WAIT; + } + for (;;) { - int ret = __ulock_wait(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, val, 0); + int ret = __ulock_wait(WAIT_FLAG | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { if (*f != val) { return; diff --git a/src/types.cpp b/src/types.cpp index 944760142..a9a7d6dda 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1474,6 +1474,7 @@ gb_internal i64 matrix_align_of(Type *t, struct TypePath *tp) { Type *elem = t->Matrix.elem; i64 row_count = gb_max(t->Matrix.row_count, 1); + i64 column_count = gb_max(t->Matrix.column_count, 1); bool pop = type_path_push(tp, elem); if (tp->failure) { @@ -1491,7 +1492,7 @@ gb_internal i64 matrix_align_of(Type *t, struct TypePath *tp) { // could be maximally aligned but as a compromise, having no padding will be // beneficial to third libraries that assume no padding - i64 total_expected_size = row_count*t->Matrix.column_count*elem_size; + i64 total_expected_size = row_count*column_count*elem_size; // i64 min_alignment = prev_pow2(elem_align * row_count); i64 min_alignment = prev_pow2(total_expected_size); while (total_expected_size != 0 && (total_expected_size % min_alignment) != 0) { @@ -1523,12 +1524,15 @@ gb_internal i64 matrix_type_stride_in_bytes(Type *t, struct TypePath *tp) { i64 stride_in_bytes = 0; // NOTE(bill, 2021-10-25): The alignment strategy here is to have zero padding - // It would be better for performance to pad each column so that each column + // It would be better for performance to pad each column/row so that each column/row // could be maximally aligned but as a compromise, having no padding will be // beneficial to third libraries that assume no padding - i64 row_count = t->Matrix.row_count; - stride_in_bytes = elem_size*row_count; - + + if (t->Matrix.is_row_major) { + stride_in_bytes = elem_size*t->Matrix.column_count; + } else { + stride_in_bytes = elem_size*t->Matrix.row_count; + } t->Matrix.stride_in_bytes = stride_in_bytes; return stride_in_bytes; } @@ -2091,6 +2095,9 @@ gb_internal bool is_type_valid_vector_elem(Type *t) { if (is_type_boolean(t)) { return true; } + if (t->Basic.kind == Basic_rawptr) { + return true; + } } return false; } @@ -2807,8 +2814,14 @@ gb_internal bool are_types_identical_internal(Type *x, Type *y, bool check_tuple case Type_Union: if (x->Union.variants.count == y->Union.variants.count && - x->Union.custom_align == y->Union.custom_align && x->Union.kind == y->Union.kind) { + + if (x->Union.custom_align != y->Union.custom_align) { + if (type_align_of(x) != type_align_of(y)) { + return false; + } + } + // NOTE(bill): zeroth variant is nullptr for_array(i, x->Union.variants) { if (!are_types_identical(x->Union.variants[i], y->Union.variants[i])) { @@ -2824,10 +2837,16 @@ gb_internal bool are_types_identical_internal(Type *x, Type *y, bool check_tuple x->Struct.is_no_copy == y->Struct.is_no_copy && x->Struct.fields.count == y->Struct.fields.count && x->Struct.is_packed == y->Struct.is_packed && - x->Struct.custom_align == y->Struct.custom_align && x->Struct.soa_kind == y->Struct.soa_kind && x->Struct.soa_count == y->Struct.soa_count && are_types_identical(x->Struct.soa_elem, y->Struct.soa_elem)) { + + if (x->Struct.custom_align != y->Struct.custom_align) { + if (type_align_of(x) != type_align_of(y)) { + return false; + } + } + for_array(i, x->Struct.fields) { Entity *xf = x->Struct.fields[i]; Entity *yf = y->Struct.fields[i]; @@ -2957,10 +2976,7 @@ gb_internal Type *c_vararg_promote_type(Type *type) { GB_ASSERT(type != nullptr); Type *core = core_type(type); - - if (core->kind == Type_BitSet) { - core = core_type(bit_set_to_int(core)); - } + GB_ASSERT(core->kind != Type_BitSet); if (core->kind == Type_Basic) { switch (core->Basic.kind) { @@ -4205,7 +4221,11 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) { case Type_Matrix: { i64 stride_in_bytes = matrix_type_stride_in_bytes(t, path); - return stride_in_bytes * t->Matrix.column_count; + if (t->Matrix.is_row_major) { + return stride_in_bytes * t->Matrix.row_count; + } else { + return stride_in_bytes * t->Matrix.column_count; + } } case Type_BitField: diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 000000000..e8cdc9e79 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,22 @@ +# Ignore all files. +* + +# Un-ignore all directories. +!*/ + +# Un-ignore files with an extension. +# +# In conjunction with the first two rules, this should catch extensionless +# binaries on the UNIX-like platforms. +!*.* + +# But remember to ignore executables with an extension. +*.exe +*.bin +*.bat +*.pdb +*.sh + +# Ignore documentation-related files. +/documentation/verify/ +/documentation/all.odin-doc diff --git a/tests/benchmark/all.odin b/tests/benchmark/all.odin index d1b7662e2..a48872cc6 100644 --- a/tests/benchmark/all.odin +++ b/tests/benchmark/all.odin @@ -1,4 +1,6 @@ package benchmarks +@(require) import "bytes" @(require) import "crypto" @(require) import "hash" +@(require) import "text/regex" diff --git a/tests/benchmark/bytes/benchmark_bytes.odin b/tests/benchmark/bytes/benchmark_bytes.odin new file mode 100644 index 000000000..e937fd0c2 --- /dev/null +++ b/tests/benchmark/bytes/benchmark_bytes.odin @@ -0,0 +1,126 @@ +package benchmark_bytes + +import "core:bytes" +import "core:fmt" +import "core:log" +import "core:testing" +import "core:strings" +import "core:text/table" +import "core:time" + +RUNS_PER_SIZE :: 2500 + +sizes := [?]int { + 15, 16, 17, + 31, 32, 33, + 63, 64, 65, + 128, + 256, + 512, + 1024, + 4096, + 1024 * 1024, + // 1024 * 1024 * 1024, +} + +// These are the normal, unoptimized algorithms. + +plain_index_byte :: proc(s: []u8, c: byte) -> (res: int) #no_bounds_check { + for i := 0; i < len(s); i += 1 { + if s[i] == c { + return i + } + } + return -1 +} + +plain_last_index_byte :: proc(s: []u8, c: byte) -> (res: int) #no_bounds_check { + for i := len(s)-1; i >= 0; i -= 1 { + if s[i] == c { + return i + } + } + return -1 +} + +run_trial_size :: proc(p: proc([]u8, byte) -> int, size: int, idx: int, runs: int) -> (timing: time.Duration) { + data := make([]u8, size) + defer delete(data) + + for i in 0.. int, simd: proc([]u8, byte) -> int) { + string_buffer := strings.builder_make() + defer strings.builder_destroy(&string_buffer) + + tbl: table.Table + table.init(&tbl) + defer table.destroy(&tbl) + + // table.caption(&tbl, "index_byte benchmark") + table.aligned_header_of_values(&tbl, .Right, "Algorithm", "Size", "Iterations", "Scalar", "SIMD", "SIMD Relative (%)", "SIMD Relative (x)") + + for size in sizes { + needle_index := size - 1 if forward else 0 + + plain_timing := run_trial_size(plain, size, needle_index, RUNS_PER_SIZE) + simd_timing := run_trial_size(simd, size, needle_index, RUNS_PER_SIZE) + + _plain := fmt.tprintf("%8M", plain_timing) + _simd := fmt.tprintf("%8M", simd_timing) + _relp := fmt.tprintf("%.3f %%", f64(simd_timing) / f64(plain_timing) * 100.0) + _relx := fmt.tprintf("%.3f x", 1 / (f64(simd_timing) / f64(plain_timing))) + + table.aligned_row_of_values( + &tbl, + .Right, + algo_name, + size, RUNS_PER_SIZE, _plain, _simd, _relp, _relx) + } + + builder_writer := strings.to_writer(&string_buffer) + + fmt.sbprintln(&string_buffer) + table.write_plain_table(builder_writer, &tbl) + + my_table_string := strings.to_string(string_buffer) + log.info(my_table_string) +} + +@test +benchmark_index_byte :: proc(t: ^testing.T) { + bench_table("index_byte", true, plain_index_byte, bytes.index_byte) + // bench_table("last_index_byte", false, plain_last_index_byte, bytes.last_index_byte) +} + +/* +@test +benchmark_simd_index_hot :: proc(t: ^testing.T) { + report: string + for size in sizes { + timing := run_trial_size(bytes.index_byte, size, size - 1, HOT, HOT) + report = fmt.tprintf("%s\n +++ % 8M | %v", report, size, timing) + timing = run_trial_size(bytes.last_index_byte, size, 0, HOT, HOT) + report = fmt.tprintf("%s\n (last) +++ % 8M | %v", report, size, timing) + } + log.info(report) +} +*/ \ No newline at end of file diff --git a/tests/benchmark/crypto/benchmark_crypto.odin b/tests/benchmark/crypto/benchmark_crypto.odin index b2ac4bca3..b139ea669 100644 --- a/tests/benchmark/crypto/benchmark_crypto.odin +++ b/tests/benchmark/crypto/benchmark_crypto.odin @@ -279,13 +279,13 @@ _benchmark_chacha20 :: proc( 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, } - nonce := [chacha20.NONCE_SIZE]byte { + iv := [chacha20.IV_SIZE]byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } ctx: chacha20.Context = --- - chacha20.init(&ctx, key[:], nonce[:]) + chacha20.init(&ctx, key[:], iv[:]) for _ in 0 ..= options.rounds { chacha20.xor_bytes(&ctx, buf, buf) @@ -334,15 +334,18 @@ _benchmark_chacha20poly1305 :: proc( 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, } - nonce := [chacha20.NONCE_SIZE]byte { + iv := [chacha20.IV_SIZE]byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } + ctx: chacha20poly1305.Context = --- + chacha20poly1305.init(&ctx, key[:]) // Basically 0 overhead. + tag: [chacha20poly1305.TAG_SIZE]byte = --- for _ in 0 ..= options.rounds { - chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) + chacha20poly1305.seal(&ctx, buf, tag[:], iv[:], nil, buf) } options.count = options.rounds options.processed = options.rounds * options.bytes @@ -363,13 +366,13 @@ _benchmark_aes256_ctr :: proc( 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, } - nonce := [aes.CTR_IV_SIZE]byte { + iv := [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[:]) + aes.init_ctr(&ctx, key[:], iv[:]) for _ in 0 ..= options.rounds { aes.xor_bytes_ctr(&ctx, buf, buf) @@ -386,13 +389,13 @@ _benchmark_aes256_gcm :: proc( err: time.Benchmark_Error, ) { buf := options.input - nonce: [aes.GCM_NONCE_SIZE]byte + iv: [aes.GCM_IV_SIZE]byte tag: [aes.GCM_TAG_SIZE]byte = --- - ctx := transmute(^aes.Context_GCM)context.user_ptr + ctx := (^aes.Context_GCM)(context.user_ptr) for _ in 0 ..= options.rounds { - aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) + aes.seal_gcm(ctx, buf, tag[:], iv[:], nil, buf) } options.count = options.rounds options.processed = options.rounds * options.bytes diff --git a/tests/benchmark/text/regex/benchmark_regex.odin b/tests/benchmark/text/regex/benchmark_regex.odin new file mode 100644 index 000000000..8d29888a3 --- /dev/null +++ b/tests/benchmark/text/regex/benchmark_regex.odin @@ -0,0 +1,258 @@ +package benchmark_core_text_regex + +import "core:fmt" +import "core:log" +import "core:math/rand" +import "core:mem" +import "core:testing" +import "core:text/regex" +import "core:time" +import "core:unicode/utf8" + +randomize_ascii :: proc(data: []u8) { + for i in 0.. len(data) - i { + continue + } + + r_data, size := utf8.encode_rune(r) + for j in 0..[%i] to be true", + ba.length, ba.bias, i) + } + + seen: [dynamic]int + defer delete(seen) + + iter := bit_array.make_iterator(ba) + for i in bit_array.iterate_by_set(&iter) { + append(&seen, i) + } + + testing.expectf(t, slice.equal(list, seen[:]), + "Expected bit_array to be: %v, got %v", + ba.length, ba.bias, list, seen) + } +} + +@test +test_bit_array_empty_iteration :: proc(t: ^testing.T) { + ba: ^bit_array.Bit_Array = &{} + defer bit_array.destroy(ba) + + for x in 0..=1 { + if x == 1 { + // Run the same tests with a created bit_array. + ba = bit_array.create(0,0) + } + + iter := bit_array.make_iterator(ba) + for v, i in bit_array.iterate_by_all(&iter) { + log.errorf("Empty bit array had iterable: %v, %i", v, i) + } + + iter = bit_array.make_iterator(ba) + for i in bit_array.iterate_by_unset(&iter) { + log.errorf("Empty bit array had iterable: %v", i) + } + } +} + +@test +test_bit_array_biased_max_index :: proc(t: ^testing.T) { + for bias in -ELEM_BIT_SIZE..=ELEM_BIT_SIZE { + for max_index in 1+bias.. length to be: %i, got %i", + max_index, bias, expected, ba.length) + + list := make([]int, length) + defer delete(list) + for i in 0.. to contain: %v, got %v", + max_index, bias, list, seen) + } + } +} + +@test +test_bit_array_shrink :: proc(t: ^testing.T) { + for bias in -ELEM_BIT_SIZE..=ELEM_BIT_SIZE { + ba := bit_array.create(bias, bias) + defer bit_array.destroy(ba) + + N :: 3*ELEM_BIT_SIZE + + for i in 0..=N { + biased_i := bias + i + bit_array.set(ba, biased_i) + + testing.expectf(t, bit_array.get(ba, biased_i), + "Expected bit_array[%i] to be true", + ba.bias, biased_i) + testing.expectf(t, ba.length == 1 + i, + "Expected bit_array length to be %i, got %i", + ba.bias, 1 + i, ba.length) + + legs := 1 + i / ELEM_BIT_SIZE + + testing.expectf(t, len(ba.bits) == legs, + "Expected bit_array to have %i legs with index %i set, had %i legs", + ba.bias, legs, biased_i, len(ba.bits)) + + bit_array.unset(ba, biased_i) + + if i >= ELEM_BIT_SIZE { + // Test shrinking arrays with bits set across two legs. + bit_array.set(ba, bias) + bit_array.shrink(ba) + + testing.expectf(t, ba.length == 1, + "Expected bit_array length to be 1 after >1 leg shrink, got %i", + ba.bias, ba.length) + testing.expectf(t, len(ba.bits) == 1, + "Expected bit_array to have one leg after >1 leg shrink, had %i", + ba.bias, len(ba.bits)) + + bit_array.unset(ba, bias) + } + + bit_array.shrink(ba) + + testing.expectf(t, ba.length == 0, + "Expected bit_array length to be zero after final shrink, got %i", + ba.bias, ba.length) + testing.expectf(t, len(ba.bits) == 0, + "Expected bit_array to have zero legs with index %i set after final shrink, had %i", + ba.bias, biased_i, len(ba.bits)) + } + } +} + +@test +test_bit_array :: proc(t: ^testing.T) { + ba := bit_array.create(0, 0) + defer bit_array.destroy(ba) + + list_set: [dynamic]int + seen_set: [dynamic]int + list_unset: [dynamic]int + seen_unset: [dynamic]int + defer { + delete(list_set) + delete(seen_set) + delete(list_unset) + delete(seen_unset) + } + + // Setup bits. + MAX_INDEX :: 1+16*ELEM_BIT_SIZE + for i in 0..=MAX_INDEX { + append(&list_unset, i) + } + for i in 1..=16 { + for j in -1..=1 { + n := ELEM_BIT_SIZE * i + j + bit_array.set(ba, n) + append(&list_set, n) + } + } + #reverse for i in list_set { + ordered_remove(&list_unset, i) + } + + // Test iteration. + iter := bit_array.make_iterator(ba) + for i in bit_array.iterate_by_set(&iter) { + append(&seen_set, i) + } + testing.expectf(t, slice.equal(list_set[:], seen_set[:]), + "Expected set bit_array to be: %v, got %v", + list_set, seen_set) + + iter = bit_array.make_iterator(ba) + for i in bit_array.iterate_by_unset(&iter) { + append(&seen_unset, i) + } + testing.expectf(t, slice.equal(list_unset[:], seen_unset[:]), + "Expected unset bit_array to be: %v, got %v", + list_unset, seen_unset) + + // Test getting. + for i in list_set { + testing.expectf(t, bit_array.get(ba, i), + "Expected index %i to be true, got false", + i) + } + for i in list_unset { + testing.expectf(t, bit_array.get(ba, i) == false, + "Expected index %i to be false, got true", + i) + } + + // Test flipping bits. + rand.shuffle(list_set[:]) + rand.shuffle(list_unset[:]) + + for i in list_set { + bit_array.unset(ba, i) + testing.expectf(t, bit_array.get(ba, i) == false, + "Expected index %i to be false after unsetting, got true", + i) + } + + for i in list_unset { + bit_array.set(ba, i) + testing.expectf(t, bit_array.get(ba, i), + "Expected index %i to be true after setting, got false", + i) + } + + // Test clearing. + bit_array.clear(ba) + iter = bit_array.make_iterator(ba) + for i in 0..=MAX_INDEX { + testing.expectf(t, bit_array.get(ba, i) == false, + "Expected index %i to be false after clearing, got true", + i) + } +} diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin index b686ef6dd..bdd23691c 100644 --- a/tests/core/container/test_core_rbtree.odin +++ b/tests/core/container/test_core_rbtree.odin @@ -187,7 +187,7 @@ validate_rbtree :: proc(t: ^testing.T, tree: ^$T/rb.Tree($Key, $Value)) { } verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { - testing.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.") + testing.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.") if n == nil { return } diff --git a/tests/core/container/test_core_small_array.odin b/tests/core/container/test_core_small_array.odin index 580df793e..21f35f112 100644 --- a/tests/core/container/test_core_small_array.odin +++ b/tests/core/container/test_core_small_array.odin @@ -35,6 +35,25 @@ test_small_array_inject_at :: proc(t: ^testing.T) { testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) } +@(test) +test_small_array_push_back_elems :: proc(t: ^testing.T) { + array: small_array.Small_Array(2, int) + testing.expect(t, slice_equal(small_array.slice(&array), []int { })) + testing.expect(t, small_array.append(&array, 0), "Expected to be able to append to empty small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0 })) + testing.expect(t, small_array.append(&array, 1, 2) == false, "Expected to fail appending multiple elements beyond capacity of small array") + testing.expect(t, small_array.append(&array, 1), "Expected to be able to append to small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 1 })) + testing.expect(t, small_array.append(&array, 1) == false, "Expected to fail appending to full small array") + testing.expect(t, small_array.append(&array, 1, 2) == false, "Expected to fail appending multiple elements to full small array") + small_array.clear(&array) + testing.expect(t, slice_equal(small_array.slice(&array), []int { })) + testing.expect(t, small_array.append(&array, 1, 2, 3) == false, "Expected to fail appending multiple elements to empty small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { })) + testing.expect(t, small_array.append(&array, 1, 2), "Expected to be able to append multiple elements to empty small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2 })) +} + slice_equal :: proc(a, b: []int) -> bool { if len(a) != len(b) { return false diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index f3f76646b..b3eb6e041 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -19,15 +19,39 @@ import "base:runtime" import "core:log" import "core:crypto" +import chacha_simd128 "core:crypto/_chacha20/simd128" +import chacha_simd256 "core:crypto/_chacha20/simd256" import "core:crypto/chacha20" -import "core:crypto/chacha20poly1305" +import "core:crypto/sha2" +@(private) _PLAINTEXT_SUNSCREEN_STR := "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it." @(test) test_chacha20 :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + impls := supported_chacha_impls() + + for impl in impls { + test_chacha20_stream(t, impl) + } +} + +supported_chacha_impls :: proc() -> [dynamic]chacha20.Implementation { + impls := make([dynamic]chacha20.Implementation, 0, 3, context.temp_allocator) + append(&impls, chacha20.Implementation.Portable) + if chacha_simd128.is_performant() { + append(&impls, chacha20.Implementation.Simd128) + } + if chacha_simd256.is_performant() { + append(&impls, chacha20.Implementation.Simd256) + } + + return impls +} + +test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) { // Test cases taken from RFC 8439, and draft-irtf-cfrg-xchacha-03 plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) @@ -38,7 +62,7 @@ test_chacha20 :: proc(t: ^testing.T) { 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, } - nonce := [chacha20.NONCE_SIZE]byte { + iv := [chacha20.IV_SIZE]byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, } @@ -64,7 +88,7 @@ test_chacha20 :: proc(t: ^testing.T) { derived_ciphertext: [114]byte ctx: chacha20.Context = --- - chacha20.init(&ctx, key[:], nonce[:]) + chacha20.init(&ctx, key[:], iv[:], impl) chacha20.seek(&ctx, 1) // The test vectors start the counter at 1. chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) @@ -72,7 +96,8 @@ test_chacha20 :: proc(t: ^testing.T) { testing.expectf( t, derived_ciphertext_str == ciphertext_str, - "Expected %s for xor_bytes(plaintext_str), but got %s instead", + "chacha20/%v: Expected %s for xor_bytes(plaintext_str), but got %s instead", + impl, ciphertext_str, derived_ciphertext_str, ) @@ -84,7 +109,7 @@ test_chacha20 :: proc(t: ^testing.T) { 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, } - xnonce := [chacha20.XNONCE_SIZE]byte { + xiv := [chacha20.XIV_SIZE]byte { 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, @@ -109,7 +134,7 @@ test_chacha20 :: proc(t: ^testing.T) { } xciphertext_str := string(hex.encode(xciphertext[:], context.temp_allocator)) - chacha20.init(&ctx, xkey[:], xnonce[:]) + chacha20.init(&ctx, xkey[:], xiv[:], impl) chacha20.seek(&ctx, 1) chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) @@ -117,128 +142,44 @@ test_chacha20 :: proc(t: ^testing.T) { testing.expectf( t, derived_ciphertext_str == xciphertext_str, - "Expected %s for xor_bytes(plaintext_str), but got %s instead", + "chacha20/%v: Expected %s for xor_bytes(plaintext_str), but got %s instead", + impl, xciphertext_str, derived_ciphertext_str, ) -} -@(test) -test_chacha20poly1305 :: proc(t: ^testing.T) { - plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) + // Incrementally read 1, 2, 3, ..., 2048 bytes of keystream, and + // compare the SHA-512/256 digest with a known value. Results + // and testcase taken from a known good implementation by the + // same author as the Odin test case. - aad := [12]byte { - 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, + tmp := make([]byte, 2048, context.temp_allocator) + + mem.zero(&key, size_of(key)) + mem.zero(&iv, size_of(iv)) + chacha20.init(&ctx, key[:], iv[:], impl) + + h_ctx: sha2.Context_512 + sha2.init_512_256(&h_ctx) + + for i := 1; i <= 2048; i = i + 1 { + chacha20.keystream_bytes(&ctx, tmp[:i]) + sha2.update(&h_ctx, tmp[:i]) } - key := [chacha20poly1305.KEY_SIZE]byte { - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, - 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, - } + digest: [32]byte + sha2.final(&h_ctx, digest[:]) + digest_str := string(hex.encode(digest[:], context.temp_allocator)) - nonce := [chacha20poly1305.NONCE_SIZE]byte { - 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, - 0x44, 0x45, 0x46, 0x47, - } - - ciphertext := [114]byte { - 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, - 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, - 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, - 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, - 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, - 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, - 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, - 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, - 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, - 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, - 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, - 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, - 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, - 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, - 0x61, 0x16, - } - ciphertext_str := string(hex.encode(ciphertext[:], context.temp_allocator)) - - tag := [chacha20poly1305.TAG_SIZE]byte { - 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, - 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91, - } - tag_str := string(hex.encode(tag[:], context.temp_allocator)) - - derived_tag: [chacha20poly1305.TAG_SIZE]byte - derived_ciphertext: [114]byte - - chacha20poly1305.encrypt( - derived_ciphertext[:], - derived_tag[:], - key[:], - nonce[:], - aad[:], - plaintext, - ) - - derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) + expected_digest_str := "cfd6e949225b854fe04946491e6935ff05ff983d1554bc885bca0ec8082dd5b8" testing.expectf( t, - derived_ciphertext_str == ciphertext_str, - "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", - ciphertext_str, - derived_ciphertext_str, + expected_digest_str == digest_str, + "chacha20/%v: Expected %s for keystream digest, but got %s instead", + impl, + expected_digest_str, + digest_str, ) - - derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - testing.expectf( - t, - derived_tag_str == tag_str, - "Expected tag %s for encrypt(aad, plaintext), but got %s instead", - tag_str, - derived_tag_str, - ) - - derived_plaintext: [114]byte - ok := chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - ciphertext[:], - ) - derived_plaintext_str := string(derived_plaintext[:]) - testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") - testing.expectf( - t, - derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, - "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", - _PLAINTEXT_SUNSCREEN_STR, - derived_plaintext_str, - ) - - derived_ciphertext[0] ~= 0xa5 - ok = chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - derived_ciphertext[:], - ) - testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") - - aad[0] ~= 0xa5 - ok = chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - ciphertext[:], - ) - testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") } @(test) diff --git a/tests/core/crypto/test_core_crypto_aead.odin b/tests/core/crypto/test_core_crypto_aead.odin new file mode 100644 index 000000000..90eedc0b2 --- /dev/null +++ b/tests/core/crypto/test_core_crypto_aead.odin @@ -0,0 +1,339 @@ +package test_core_crypto + +import "base:runtime" +import "core:crypto/aead" +import "core:encoding/hex" +import "core:testing" + +@(test) +test_aead :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + aes_impls := make([dynamic]aead.Implementation, context.temp_allocator) + for impl in supported_aes_impls() { + append(&aes_impls, impl) + } + chacha_impls := make([dynamic]aead.Implementation, context.temp_allocator) + for impl in supported_chacha_impls() { + append(&chacha_impls, impl) + } + impls := [aead.Algorithm][dynamic]aead.Implementation{ + .Invalid = nil, + .AES_GCM_128 = aes_impls, + .AES_GCM_192 = aes_impls, + .AES_GCM_256 = aes_impls, + .CHACHA20POLY1305 = chacha_impls, + .XCHACHA20POLY1305 = chacha_impls, + } + + test_vectors := []struct{ + algo: aead.Algorithm, + key: string, + iv: string, + aad: string, + plaintext: string, + ciphertext: string, + tag: string, + } { + // AES-GCM + // - https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf + // + // Note: NIST did a reorg of their site, so the source of the test vectors + // is only available from an archive. + { + .AES_GCM_128, + "00000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "58e2fccefa7e3061367f1d57a4e7455a", + }, + { + .AES_GCM_128, + "00000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "0388dace60b6a392f328c2b971b2fe78", + "ab6e47d42cec13bdf53a67b21257bddf", + }, + { + .AES_GCM_128, + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985", + "4d5c2af327cd64a62cf35abd2ba6fab4", + }, + { + .AES_GCM_128, + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091", + "5bc94fbc3221a5db94fae95ae7121a47", + }, + { + .AES_GCM_128, + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598", + "3612d2e79e3b0785561be14aaca2fccb", + }, + { + .AES_GCM_128, + "feffe9928665731c6d6a8f9467308308", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5", + "619cc5aefffe0bfa462af43c1699d050", + }, + { + .AES_GCM_192, + "000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "cd33b28ac773f74ba00ed1f312572435", + }, + { + .AES_GCM_192, + "000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "98e7247c07f0fe411c267e4384b0f600", + "2ff58d80033927ab8ef4d4587514f0fb", + }, + { + .AES_GCM_192, + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256", + "9924a7c8587336bfb118024db8674a14", + }, + { + .AES_GCM_192, + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710", + "2519498e80f1478f37ba55bd6d27618c", + }, + { + .AES_GCM_192, + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "0f10f599ae14a154ed24b36e25324db8c566632ef2bbb34f8347280fc4507057fddc29df9a471f75c66541d4d4dad1c9e93a19a58e8b473fa0f062f7", + "65dcc57fcf623a24094fcca40d3533f8", + }, + { + .AES_GCM_192, + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "d27e88681ce3243c4830165a8fdcf9ff1de9a1d8e6b447ef6ef7b79828666e4581e79012af34ddd9e2f037589b292db3e67c036745fa22e7e9b7373b", + "dcf566ff291c25bbb8568fc3d376a6d9", + }, + { + .AES_GCM_256, + "0000000000000000000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "530f8afbc74536b9a963b4f1c4cb738b", + }, + { + .AES_GCM_256, + "0000000000000000000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "cea7403d4d606b6e074ec5d3baf39d18", + "d0d1c8a799996bf0265b98b5d48ab919", + }, + { + .AES_GCM_256, + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad", + "b094dac5d93471bdec1a502270e3cc6c", + }, + { + .AES_GCM_256, + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662", + "76fc6ece0f4e1768cddf8853bb2d551b", + }, + { + .AES_GCM_256, + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "c3762df1ca787d32ae47c13bf19844cbaf1ae14d0b976afac52ff7d79bba9de0feb582d33934a4f0954cc2363bc73f7862ac430e64abe499f47c9b1f", + "3a337dbf46a792c45e454913fe2ea8f2", + }, + { + .AES_GCM_256, + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "5a8def2f0c9e53f1f75d7853659e2a20eeb2b22aafde6419a058ab4f6f746bf40fc0c3b780f244452da3ebf1c5d82cdea2418997200ef82e44ae7e3f", + "a44a8266ee1c8eb0c8b5d4cf5ae9f19a", + }, + // Chacha20-Poly1305 + // https://www.rfc-editor.org/rfc/rfc8439 + { + .CHACHA20POLY1305, + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + "070000004041424344454647", + "50515253c0c1c2c3c4c5c6c7", + string(hex.encode(transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR), context.temp_allocator)), + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116", + "1ae10b594f09e26a7e902ecbd0600691", + }, + // XChaCha20-Poly1305-IETF + // - https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03 + { + .XCHACHA20POLY1305, + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + "404142434445464748494a4b4c4d4e4f5051525354555657", + "50515253c0c1c2c3c4c5c6c7", + "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e", + "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b4522f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff921f9664c97637da9768812f615c68b13b52e", + "c0875924c1c7987947deafd8780acf49", + }, + } + for v, _ in test_vectors { + algo_name := aead.ALGORITHM_NAMES[v.algo] + + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + iv, _ := hex.decode(transmute([]byte)(v.iv), context.temp_allocator) + aad, _ := hex.decode(transmute([]byte)(v.aad), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + tag, _ := hex.decode(transmute([]byte)(v.tag), context.temp_allocator) + + tag_ := make([]byte, len(tag), context.temp_allocator) + dst := make([]byte, len(ciphertext), context.temp_allocator) + + ctx: aead.Context + for impl in impls[v.algo] { + aead.init(&ctx, v.algo, key, impl) + + aead.seal(&ctx, dst, tag_, iv, aad, plaintext) + dst_str := string(hex.encode(dst, context.temp_allocator)) + tag_str := string(hex.encode(tag_, context.temp_allocator)) + testing.expectf( + t, + dst_str == v.ciphertext && tag_str == v.tag, + "%s/%v: Expected: (%s, %s) for seal_ctx(%s, %s, %s, %s), but got (%s, %s) instead", + algo_name, + impl, + v.ciphertext, + v.tag, + v.key, + v.iv, + v.aad, + v.plaintext, + dst_str, + tag_str, + ) + + aead.seal(v.algo, dst, tag_, key, iv, aad, plaintext, impl) + dst_str = string(hex.encode(dst, context.temp_allocator)) + tag_str = string(hex.encode(tag_, context.temp_allocator)) + testing.expectf( + t, + dst_str == v.ciphertext && tag_str == v.tag, + "%s/%v: Expected: (%s, %s) for seal_oneshot(%s, %s, %s, %s), but got (%s, %s) instead", + algo_name, + impl, + v.ciphertext, + v.tag, + v.key, + v.iv, + v.aad, + v.plaintext, + dst_str, + tag_str, + ) + + ok := aead.open(&ctx, dst, iv, aad, ciphertext, tag) + dst_str = string(hex.encode(dst, context.temp_allocator)) + testing.expectf( + t, + ok && dst_str == v.plaintext, + "%s/%v: Expected: (%s, true) for open_ctx(%s, %s, %s, %s, %s), but got (%s, %v) instead", + algo_name, + impl, + v.plaintext, + v.key, + v.iv, + v.aad, + v.ciphertext, + v.tag, + dst_str, + ok, + ) + + ok = aead.open(v.algo, dst, key, iv, aad, ciphertext, tag, impl) + dst_str = string(hex.encode(dst, context.temp_allocator)) + testing.expectf( + t, + ok && dst_str == v.plaintext, + "%s/%v: Expected: (%s, true) for open_oneshot(%s, %s, %s, %s, %s), but got (%s, %v) instead", + algo_name, + impl, + v.plaintext, + v.key, + v.iv, + v.aad, + v.ciphertext, + v.tag, + dst_str, + ok, + ) + + tag_[0] ~= 0xa5 + ok = aead.open(&ctx, dst, iv, aad, ciphertext, tag_) + testing.expectf(t, !ok, "%s/%v: Expected false for open(bad_tag, aad, ciphertext)", algo_name, impl) + + if len(dst) > 0 { + copy(dst, ciphertext[:]) + dst[0] ~= 0xa5 + ok = aead.open(&ctx, dst, iv, aad, dst, tag) + testing.expectf(t, !ok, "%s/%v: Expected false for open(tag, aad, bad_ciphertext)", algo_name, impl) + } + + if len(aad) > 0 { + aad_ := make([]byte, len(aad), context.temp_allocator) + copy(aad_, aad) + aad_[0] ~= 0xa5 + ok = aead.open(&ctx, dst, iv, aad_, ciphertext, tag) + testing.expectf(t, !ok, "%s/%v: Expected false for open(tag, bad_aad, ciphertext)", algo_name, impl) + } + } + } +} diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin index c2fa2835c..b68b30976 100644 --- a/tests/core/crypto/test_core_crypto_aes.odin +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -12,18 +12,22 @@ import "core:crypto/sha2" test_aes :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - impls := make([dynamic]aes.Implementation, 0, 2) - defer delete(impls) + impls := supported_aes_impls() + + for impl in impls { + test_aes_ecb(t, impl) + test_aes_ctr(t, impl) + } +} + +supported_aes_impls :: proc() -> [dynamic]aes.Implementation { + impls := make([dynamic]aes.Implementation, 0, 2, context.temp_allocator) append(&impls, aes.Implementation.Portable) if aes.is_hardware_accelerated() { append(&impls, aes.Implementation.Hardware) } - for impl in impls { - test_aes_ecb(t, impl) - test_aes_ctr(t, impl) - test_aes_gcm(t, impl) - } + return impls } test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { @@ -197,13 +201,13 @@ 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[:], impl) + iv: [aes.CTR_IV_SIZE]byte + aes.init_ctr(&ctx, key[:], iv[:], impl) h_ctx: sha2.Context_512 sha2.init_512_256(&h_ctx) - for i := 1; i < 2048; i = i + 1 { + for i := 1; i <= 2048; i = i + 1 { aes.keystream_bytes_ctr(&ctx, tmp[:i]) sha2.update(&h_ctx, tmp[:i]) } @@ -212,7 +216,7 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { sha2.final(&h_ctx, digest[:]) digest_str := string(hex.encode(digest[:], context.temp_allocator)) - expected_digest_str := "d4445343afeb9d1237f95b10d00358aed4c1d7d57c9fe480cd0afb5e2ffd448c" + expected_digest_str := "b5ba4e7d6e3d1ff2bb54387fc1528573a6b351610ce7bcc80b00da089f4b1bf0" testing.expectf( t, expected_digest_str == digest_str, @@ -222,223 +226,3 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { digest_str, ) } - -test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { - 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 - // for non-96-bit IVs which our implementation does not support. - // - // https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf - test_vectors := []struct { - key: string, - iv: string, - aad: string, - plaintext: string, - ciphertext: string, - tag: string, - } { - { - "00000000000000000000000000000000", - "000000000000000000000000", - "", - "", - "", - "58e2fccefa7e3061367f1d57a4e7455a", - }, - { - "00000000000000000000000000000000", - "000000000000000000000000", - "", - "00000000000000000000000000000000", - "0388dace60b6a392f328c2b971b2fe78", - "ab6e47d42cec13bdf53a67b21257bddf", - }, - { - "feffe9928665731c6d6a8f9467308308", - "cafebabefacedbaddecaf888", - "", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", - "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985", - "4d5c2af327cd64a62cf35abd2ba6fab4", - }, - { - "feffe9928665731c6d6a8f9467308308", - "cafebabefacedbaddecaf888", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091", - "5bc94fbc3221a5db94fae95ae7121a47", - }, - /* - { - "feffe9928665731c6d6a8f9467308308", - "cafebabefacedbad", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598", - "3612d2e79e3b0785561be14aaca2fccb", - }, - { - "feffe9928665731c6d6a8f9467308308", - "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5", - "619cc5aefffe0bfa462af43c1699d050", - }, - */ - { - "000000000000000000000000000000000000000000000000", - "000000000000000000000000", - "", - "", - "", - "cd33b28ac773f74ba00ed1f312572435", - }, - { - "000000000000000000000000000000000000000000000000", - "000000000000000000000000", - "", - "00000000000000000000000000000000", - "98e7247c07f0fe411c267e4384b0f600", - "2ff58d80033927ab8ef4d4587514f0fb", - }, - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c", - "cafebabefacedbaddecaf888", - "", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", - "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256", - "9924a7c8587336bfb118024db8674a14", - }, - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c", - "cafebabefacedbaddecaf888", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710", - "2519498e80f1478f37ba55bd6d27618c", - }, - /* - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c", - "cafebabefacedbad", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "0f10f599ae14a154ed24b36e25324db8c566632ef2bbb34f8347280fc4507057fddc29df9a471f75c66541d4d4dad1c9e93a19a58e8b473fa0f062f7", - "65dcc57fcf623a24094fcca40d3533f8", - }, - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c", - "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "d27e88681ce3243c4830165a8fdcf9ff1de9a1d8e6b447ef6ef7b79828666e4581e79012af34ddd9e2f037589b292db3e67c036745fa22e7e9b7373b", - "dcf566ff291c25bbb8568fc3d376a6d9", - }, - */ - { - "0000000000000000000000000000000000000000000000000000000000000000", - "000000000000000000000000", - "", - "", - "", - "530f8afbc74536b9a963b4f1c4cb738b", - }, - { - "0000000000000000000000000000000000000000000000000000000000000000", - "000000000000000000000000", - "", - "00000000000000000000000000000000", - "cea7403d4d606b6e074ec5d3baf39d18", - "d0d1c8a799996bf0265b98b5d48ab919", - }, - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", - "cafebabefacedbaddecaf888", - "", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", - "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad", - "b094dac5d93471bdec1a502270e3cc6c", - }, - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", - "cafebabefacedbaddecaf888", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662", - "76fc6ece0f4e1768cddf8853bb2d551b", - }, - /* - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", - "cafebabefacedbad", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "c3762df1ca787d32ae47c13bf19844cbaf1ae14d0b976afac52ff7d79bba9de0feb582d33934a4f0954cc2363bc73f7862ac430e64abe499f47c9b1f", - "3a337dbf46a792c45e454913fe2ea8f2", - }, - { - "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", - "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", - "feedfacedeadbeeffeedfacedeadbeefabaddad2", - "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", - "5a8def2f0c9e53f1f75d7853659e2a20eeb2b22aafde6419a058ab4f6f746bf40fc0c3b780f244452da3ebf1c5d82cdea2418997200ef82e44ae7e3f", - "a44a8266ee1c8eb0c8b5d4cf5ae9f19a", - }, - */ - } - for v, _ in test_vectors { - key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) - iv, _ := hex.decode(transmute([]byte)(v.iv), context.temp_allocator) - aad, _ := hex.decode(transmute([]byte)(v.aad), context.temp_allocator) - plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) - ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) - tag, _ := hex.decode(transmute([]byte)(v.tag), context.temp_allocator) - - tag_ := make([]byte, len(tag), context.temp_allocator) - dst := make([]byte, len(ciphertext), context.temp_allocator) - - ctx: aes.Context_GCM - aes.init_gcm(&ctx, key, impl) - - aes.seal_gcm(&ctx, dst, tag_, iv, aad, plaintext) - dst_str := string(hex.encode(dst[:], context.temp_allocator)) - tag_str := string(hex.encode(tag_[:], context.temp_allocator)) - - testing.expectf( - t, - dst_str == v.ciphertext && tag_str == v.tag, - "AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead", - impl, - v.ciphertext, - v.tag, - v.key, - v.iv, - v.aad, - v.plaintext, - dst_str, - tag_str, - ) - - ok := aes.open_gcm(&ctx, dst, iv, aad, ciphertext, tag) - dst_str = string(hex.encode(dst[:], context.temp_allocator)) - - testing.expectf( - t, - ok && dst_str == v.plaintext, - "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %v) instead", - impl, - v.plaintext, - v.key, - v.iv, - v.aad, - v.ciphertext, - v.tag, - dst_str, - ok, - ) - } -} diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index d069ef05b..45f47505a 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -117,11 +117,25 @@ test_marshalling :: proc(t: ^testing.T) { diagnosis, eerr := cbor.to_diagnostic_format(decoded) testing.expect_value(t, eerr, nil) defer delete(diagnosis) - testing.expect_value(t, diagnosis, `{ - "base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="), - "biggest": 2(h'f951a9fd3c158afdff08ab8e0'), - "biggie": 18446744073709551615, + "no": null, + "neg": -69, + "nos": undefined, + "now": 1(1701117968), + "pos": 1212, + "str": "Hellope", + "yes": true, + "comp": [ + 32.0000, + 33.0000 + ], + "cstr": "Hellnope", + "quat": [ + 17.0000, + 18.0000, + 19.0000, + 16.0000 + ], "child": { "dyn": [ "one", @@ -148,41 +162,26 @@ test_marshalling :: proc(t: ^testing.T) { 10 ] }, - "comp": [ - 32.0000, - 33.0000 - ], - "cstr": "Hellnope", "ennie": 0, - "ennieb": 512, - "iamint": -256, - "important": "!", - "my_bytes": h'', - "neg": -69, - "no": nil, - "nos": undefined, - "now": 1(1701117968), "nowie": { "_nsec": 1701117968000000000 }, - "onetwenty": 12345, - "pos": 1212, - "quat": [ - 17.0000, - 18.0000, - 19.0000, - 16.0000 - ], - "renamed :)": 123123.12500000, - "small_onetwenty": -18446744073709551615, - "smallest": 3(h'f951a9fd3c158afdff08ab8e0'), - "smallie": -18446744073709551616, - "str": "Hellope", "value": { 16: "16 is a nice number", 32: 69 }, - "yes": true + "base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="), + "biggie": 18446744073709551615, + "ennieb": 512, + "iamint": -256, + "biggest": 2(h'0f951a9fd3c158afdff08ab8e0'), + "smallie": -18446744073709551616, + "my_bytes": h'', + "smallest": 3(h'0f951a9fd3c158afdff08ab8e0'), + "important": "!", + "onetwenty": 12345, + "renamed :)": 123123.12500000, + "small_onetwenty": -18446744073709551615 }`) backf: Foo @@ -295,7 +294,7 @@ test_marshalling_nil_maybe :: proc(t: ^testing.T) { testing.expect_value(t, derr, nil) diag := cbor.to_diagnostic_format(val) - testing.expect_value(t, diag, "nil") + testing.expect_value(t, diag, "null") delete(diag) maybe_dest: Maybe(int) @@ -439,7 +438,7 @@ test_encode_negative :: proc(t: ^testing.T) { test_decode_simples :: proc(t: ^testing.T) { expect_decoding(t, "\xf4", "false", bool) expect_decoding(t, "\xf5", "true", bool) - expect_decoding(t, "\xf6", "nil", cbor.Nil) + expect_decoding(t, "\xf6", "null", cbor.Nil) expect_decoding(t, "\xf7", "undefined", cbor.Undefined) expect_decoding(t, "\xf0", "simple(16)", cbor.Simple) @@ -503,11 +502,11 @@ test_encode_floats :: proc(t: ^testing.T) { @(test) test_decode_bytes :: proc(t: ^testing.T) { expect_decoding(t, "\x40", "h''", ^cbor.Bytes) - expect_decoding(t, "\x44\x01\x02\x03\x04", "h'1234'", ^cbor.Bytes) + expect_decoding(t, "\x44\x01\x02\x03\x04", "h'01020304'", ^cbor.Bytes) // Indefinite lengths - expect_decoding(t, "\x5f\x42\x01\x02\x43\x03\x04\x05\xff", "h'12345'", ^cbor.Bytes) + expect_decoding(t, "\x5f\x42\x01\x02\x43\x03\x04\x05\xff", "h'0102030405'", ^cbor.Bytes) } @(test) @@ -703,10 +702,10 @@ test_encode_maps :: proc(t: ^testing.T) { @(test) test_decode_tags :: proc(t: ^testing.T) { // Tag number 2 (unsigned bignumber), value bytes, max(u64) + 1. - expect_tag(t, "\xc2\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_UNSIGNED_BIG_NR, "2(h'100000000')") + expect_tag(t, "\xc2\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_UNSIGNED_BIG_NR, "2(h'010000000000000000')") // Tag number 3 (negative bignumber), value bytes, negative max(u64) - 1. - expect_tag(t, "\xc3\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_NEGATIVE_BIG_NR, "3(h'100000000')") + expect_tag(t, "\xc3\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_NEGATIVE_BIG_NR, "3(h'010000000000000000')") expect_tag(t, "\xc1\x1a\x51\x4b\x67\xb0", cbor.TAG_EPOCH_TIME_NR, "1(1363896240)") expect_tag(t, "\xc1\xfb\x41\xd4\x52\xd9\xec\x20\x00\x00", cbor.TAG_EPOCH_TIME_NR, "1(1363896240.5000000000000000)") @@ -723,16 +722,16 @@ test_encode_tags :: proc(t: ^testing.T) { // Helpers expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: typeid, loc := #caller_location) { - res, err := cbor.decode(encoded) + res, err := cbor.decode(encoded) defer cbor.destroy(res) testing.expect_value(t, reflect.union_variant_typeid(res), type, loc) - testing.expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) str := cbor.to_diagnostic_format(res, padding=-1) defer delete(str) - testing.expect_value(t, str, decoded, loc) + testing.expect_value(t, str, decoded, loc) } expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) { @@ -754,11 +753,11 @@ expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_de } expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #caller_location) where intrinsics.type_is_float(T) { - res, err := cbor.decode(encoded) + res, err := cbor.decode(encoded) defer cbor.destroy(res) testing.expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) - testing.expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) #partial switch r in res { case f16: diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 42ac9ce0f..f588d34fa 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -429,7 +429,7 @@ map_with_integer_keys :: proc(t: ^testing.T) { defer delete_map(my_map2) unmarshal_err := json.unmarshal(marshaled_data, &my_map2) - defer for key, item in my_map2 { + defer for _, item in my_map2 { runtime.delete_string(item) } testing.expectf(t, unmarshal_err == nil, "Expected `json.unmarshal` to return nil, got %v", unmarshal_err) diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index b29431e10..c0e4329bd 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -241,7 +241,7 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { written += fmt.wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident) if len(doc.doctype.rest) > 0 { - fmt.wprintf(writer, "\t%v\n", doc.doctype.rest) + fmt.wprintf(writer, "\t%v\n", doc.doctype.rest) } } @@ -250,10 +250,10 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { } if doc.element_count > 0 { - fmt.wprintln(writer, " --- ") - print_element(writer, doc, 0) - fmt.wprintln(writer, " --- ") - } + fmt.wprintln(writer, " --- ") + print_element(writer, doc, 0) + fmt.wprintln(writer, " --- ") + } return written, .None } diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin new file mode 100644 index 000000000..10c9550cb --- /dev/null +++ b/tests/core/io/test_core_io.odin @@ -0,0 +1,735 @@ +package test_core_io + +import "core:bufio" +import "core:bytes" +import "core:io" +import "core:log" +import "core:os" +import "core:os/os2" +import "core:strings" +import "core:testing" + +Passed_Tests :: distinct io.Stream_Mode_Set + +_test_stream :: proc( + t: ^testing.T, + stream: io.Stream, + buffer: []u8, + + reading_consumes: bool = false, + resets_on_empty: bool = false, + do_destroy: bool = true, + + loc := #caller_location +) -> (passed: Passed_Tests, ok: bool) { + // We only test what the stream reports to support. + + mode_set := io.query(stream) + + // Can't feature-test anything if Query isn't supported. + testing.expectf(t, .Query in mode_set, "stream does not support .Query: %v", mode_set, loc = loc) or_return + + passed += { .Query } + + size := i64(len(buffer)) + + // Do some basic Seek sanity testing. + if .Seek in mode_set { + pos, err := io.seek(stream, 0, io.Seek_From(-1)) + testing.expectf(t, err == .Invalid_Whence, + "Seek(-1) didn't fail with Invalid_Whence: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && err == nil, + "Seek Start isn't 0: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Current) + testing.expectf(t, pos == 0 && err == nil, + "Seek Current isn't 0 at the start: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, -1, .Start) + testing.expectf(t, err == .Invalid_Offset, + "Seek Start-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, -1, .Current) + testing.expectf(t, err == .Invalid_Offset, + "Seek Current-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .End) + testing.expectf(t, pos == size && err == nil, + "Seek End+0 failed: %v != size<%i>, %v", pos, size, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Current) + testing.expectf(t, pos == size && err == nil, + "Seek Current isn't size<%v> at the End: %v, %v", size, pos, err, loc = loc) or_return + + // Seeking past the End is accepted throughout the API. + // + // It's _reading_ past the End which is erroneous. + pos, err = io.seek(stream, 1, .End) + testing.expectf(t, pos == size+1 && err == nil, + "Seek End+1 failed: %v, %v", pos, err, loc = loc) or_return + + // Reset our position for future tests. + pos, err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && err == nil, + "Seek Start reset failed: %v, %v", pos, err, loc = loc) or_return + + passed += { .Seek } + } + + // Test Size. + if .Size in mode_set { + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Size reports %v for its size; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Size expected no error: %v", size_err, loc = loc) or_return + + // Ensure Size does not move the underlying pointer from the start. + // + // Some implementations may use seeking to determine file sizes. + if .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos == 0 && seek_err == nil, + "Size+Seek Current isn't 0 after getting size: %v, %v", pos, seek_err, loc = loc) or_return + } + + passed += { .Size } + } + + // Test Read_At. + if .Read_At in mode_set { + // Test reading into an empty buffer. + { + nil_slice: []u8 + bytes_read, err := io.read_at(stream, nil_slice, 0) + testing.expectf(t, bytes_read == 0 && err == nil, + "Read_At into empty slice failed: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + + read_buf, alloc_err := make([]u8, size) + testing.expect_value(t, alloc_err, nil, loc = loc) or_return + defer delete(read_buf) + + for start in 0.. != buffer<%v>", read_buf, buffer, loc = loc) or_return + } + } + + // Test empty streams and EOF. + one_buf: [1]u8 + bytes_read, err := io.read_at(stream, one_buf[:], size) + testing.expectf(t, err == .EOF, + "Read_At at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return + + // Make sure size is still sane. + if .Size in mode_set { + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Read_At+Size reports %v for its size after Read_At tests; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read_At+Size expected no error: %v", size_err, loc = loc) or_return + } + + // Ensure Read_At does not move the underlying pointer from the start. + if .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos == 0 && seek_err == nil, + "Read_At+Seek Current isn't 0 after reading: %v, %v", pos, seek_err, loc = loc) or_return + } + + passed += { .Read_At } + } + + // Test Read. + if .Read in mode_set { + // Test reading into an empty buffer. + { + nil_slice: []u8 + bytes_read, err := io.read(stream, nil_slice) + testing.expectf(t, bytes_read == 0 && err == nil, + "Read into empty slice failed: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + + if size > 0 { + read_buf, alloc_err := make([]u8, size) + testing.expectf(t, alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, err := io.read(stream, read_buf[:1]) + testing.expectf(t, bytes_read == 1 && err == nil, + "Read 1 byte at start failed: %v, %v", bytes_read, err, loc = loc) or_return + testing.expectf(t, read_buf[0] == buffer[0], + "Read of first byte failed: read_buf[0]<%v> != buffer[0]<%v>", read_buf[0], buffer[0], loc = loc) or_return + + // Test rolling back the stream one byte then reading it again. + if .Seek in mode_set { + pos, seek_err := io.seek(stream, -1, .Current) + testing.expectf(t, pos == 0 && err == nil, + "Read+Seek Current-1 reset to 0 failed: %v, %v", pos, seek_err, loc = loc) or_return + + bytes_read, err = io.read(stream, read_buf[:1]) + testing.expectf(t, bytes_read == 1 && err == nil, + "Read 1 byte at start after Seek reset failed: %v, %v", bytes_read, err, loc = loc) or_return + testing.expectf(t, read_buf[0] == buffer[0] , + "re-Read of first byte failed: read_buf[0]<%v> != buffer[0]<%v>", read_buf[0], buffer[0], loc = loc) or_return + } + + // Make sure size is still sane. + if .Size in mode_set { + api_size, size_err := io.size(stream) + expected_api_size := size - 1 if reading_consumes else size + + testing.expectf(t, api_size == expected_api_size, + "Read+Size reports %v for its size after Read tests; expected %v", api_size, expected_api_size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read+Size expected no error: %v", size_err, loc = loc) or_return + } + + // Read the rest. + if size > 1 { + bytes_read, err = io.read(stream, read_buf[1:]) + testing.expectf(t, i64(bytes_read) == size - 1 && err == nil, + "Read rest of stream failed: %v != %v, %v", bytes_read, size-1, err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, buffer) == 0, + "Read buffer compare failed: read_buf<%v> != buffer<%v>", read_buf, buffer, loc = loc) or_return + } + } + + // Test empty streams and EOF. + one_buf: [1]u8 + bytes_read, err := io.read(stream, one_buf[:]) + testing.expectf(t, err == .EOF, + "Read at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return + + if !resets_on_empty && .Size in mode_set { + // Make sure size is still sane. + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Read+Size reports %v for its size after Read tests; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read+Size expected no error: %v", size_err, loc = loc) or_return + } + + passed += { .Read } + } + + // Test Write_At. + if .Write_At in mode_set { + // Test writing from an empty buffer. + { + nil_slice: []u8 + bytes_written, err := io.write_at(stream, nil_slice, 0) + testing.expectf(t, bytes_written == 0 && err == nil, + "Write_At from empty slice failed: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + + // Ensure Write_At does not move the underlying pointer from the start. + starting_offset : i64 = -1 + if .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos >= 0 && seek_err == nil, + "Write_At+Seek Current failed: %v, %v", pos, seek_err, loc = loc) or_return + starting_offset = pos + } + + if size > 0 { + write_buf, write_buf_alloc_err := make([]u8, size) + testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(write_buf) + + for i in 0.. != size<%v>: %v", bytes_written, size, write_err, loc = loc) or_return + + // Test reading what we've written. + if .Read_At in mode_set { + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + bytes_read, read_err := io.read_at(stream, read_buf[:], 0) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write_At+Read_At failed: bytes_read<%i> != size<%i>, %v", bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write_At+Read_At buffer compare failed: write_buf<%v> != read_buf<%v>", write_buf, read_buf, loc = loc) or_return + } + } else { + // Expect that it should be okay to write a single byte to an empty stream. + x_buf: [1]u8 = { 'Z' } + + bytes_written, write_err := io.write_at(stream, x_buf[:], 0) + testing.expectf(t, i64(bytes_written) == 1 && write_err == nil, + "Write_At(0) with 'Z' on empty stream failed: bytes_written<%v>, %v", bytes_written, write_err, loc = loc) or_return + + // Test reading what we've written. + if .Read_At in mode_set { + x_buf[0] = 0 + bytes_read, read_err := io.read_at(stream, x_buf[:], 0) + testing.expectf(t, i64(bytes_read) == 1 && read_err == nil, + "Write_At(0)+Read_At(0) failed expectation: bytes_read<%v> != 1, %q != 'Z', %v", bytes_read, x_buf[0], read_err, loc = loc) or_return + } + } + + // Ensure Write_At does not move the underlying pointer from the start. + if starting_offset != -1 && .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos == starting_offset && seek_err == nil, + "Write_At+Seek Current isn't %v after writing: %v, %v", starting_offset, pos, seek_err, loc = loc) or_return + } + + passed += { .Write_At } + } + + // Test Write. + if .Write in mode_set { + // Test writing from an empty buffer. + { + nil_slice: []u8 + bytes_written, err := io.write(stream, nil_slice) + testing.expectf(t, bytes_written == 0 && err == nil, + "Write from empty slice failed: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + + write_buf, write_buf_alloc_err := make([]u8, size) + testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(write_buf) + + for i in 0.., %v", pos, seek_err) or_return + } + + // Get the Size before writing. + if .Size in mode_set { + size_err: io.Error + before_write_size, size_err = io.size(stream) + testing.expectf(t, size_err == nil, + "Write+Size failed: %v", size_err, loc = loc) or_return + } + + bytes_written, write_err := io.write(stream, write_buf[:]) + testing.expectf(t, i64(bytes_written) == size && write_err == nil, + "Write %i bytes failed: %i, %v", size, bytes_written, write_err, loc = loc) or_return + + // Size sanity check, part 2. + if before_write_size >= 0 && .Size in mode_set { + after_write_size, size_err := io.size(stream) + testing.expectf(t, size_err == nil, + "Write+Size.part_2 failed: %v", size_err, loc = loc) or_return + testing.expectf(t, after_write_size == before_write_size + size, + "Write+Size.part_2 failed: %v != %v + %v", after_write_size, before_write_size, size, loc = loc) or_return + } + + // Test reading what we've written directly with Read_At. + if pos >= 0 && .Read_At in mode_set { + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, read_err := io.read_at(stream, read_buf[:], pos) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write+Read_At(%i) failed: bytes_read<%i> != size<%i>, %v", pos, bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write+Read_At buffer compare failed: read_buf<%v> != write_buf<%v>", read_buf, write_buf, loc = loc) or_return + } + + // Test resetting the pointer and reading what we've written with Read. + if .Read in mode_set && .Seek in mode_set { + seek_err: io.Error + pos, seek_err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && seek_err == nil, + "Write+Read+Seek(Start) failed: pos<%i>, %v", pos, seek_err) or_return + + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, read_err := io.read(stream, read_buf[:]) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write+Read failed: bytes_read<%i> != size<%i>, %v", bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write+Readbuffer compare failed: read_buf<%v> != write_buf<%v>", read_buf, write_buf, loc = loc) or_return + } + + passed += { .Write } + } + + // Test the other modes. + if .Flush in mode_set { + err := io.flush(stream) + testing.expectf(t, err == nil, "stream failed to Flush: %v", err, loc = loc) or_return + passed += { .Flush } + } + + if .Close in mode_set { + close_err := io.close(stream) + testing.expectf(t, close_err == nil, "stream failed to Close: %v", close_err, loc = loc) or_return + passed += { .Close } + } + + if do_destroy && .Destroy in mode_set { + err := io.destroy(stream) + testing.expectf(t, err == nil, "stream failed to Destroy: %v", err, loc = loc) or_return + passed += { .Destroy } + } + + ok = true + return +} + + + +@test +test_bytes_reader :: proc(t: ^testing.T) { + buf: [32]u8 + for i in 0.., %v", pos, seek_err) { + return + } + + results, ok = _test_stream(t, io.limited_reader_init(&lr, bs, end), buf[:end]) + if !ok { + log.debugf("buffer[:%i] := %v", end, buf[:end]) + return + } + } + + log.debugf("%#v", results) +} + +@test +test_section_reader :: proc(t: ^testing.T) { + buf: [32]u8 + for i in 0..\n!=\nbuffer <%q>", sb.buf[:], expected_buf[:]) + + log.debugf("%#v", results) +} + +@test +test_os_file_stream :: proc(t: ^testing.T) { + defer if !testing.failed(t) { + testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) + } + + buf: [32]u8 + for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { + return + } + + flush_err := io.flush(stream) + if !testing.expectf(t, flush_err == nil, + "failed to Flush initial buffer: %v", write_err) { + return + } + + results, _ := _test_stream(t, stream, buf[:]) + + log.debugf("%#v", results) +} + +@test +test_os2_file_stream :: proc(t: ^testing.T) { + defer if !testing.failed(t) { + testing.expect_value(t, os2.remove(TEMPORARY_FILENAME), nil) + } + + buf: [32]u8 + for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { + return + } + + flush_err := io.flush(stream) + if !testing.expectf(t, flush_err == nil, + "failed to Flush initial buffer: %v", write_err) { + return + } + + // os2 file stream proc close and destroy are the same. + results, _ := _test_stream(t, stream, buf[:], do_destroy = false) + + log.debugf("%#v", results) +} + +@test +test_bufio_buffered_writer :: proc(t: ^testing.T) { + // Using a strings.Builder as the backing stream. + + sb := strings.builder_make() + defer strings.builder_destroy(&sb) + + buf: [32]u8 + expected_buf: [64]u8 + for i in 0..\n!=\nbuffer <%q>", sb.buf[:], expected_buf[:]) + + log.debugf("%#v", results) +} + +@test +test_bufio_buffered_reader :: proc(t: ^testing.T) { + // Using a bytes.Reader as the backing stream. + + buf: [32]u8 + for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { + return + } + + flush_err := io.flush(stream) + if !testing.expectf(t, flush_err == nil, + "failed to Flush initial buffer: %v", write_err) { + return + } + + // bufio.Read_Writer isn't capable of seeking, so we have to reset the os2 + // stream back to the start here. + pos, seek_err := io.seek(stream, 0, .Start) + if !testing.expectf(t, pos == 0 && seek_err == nil, + "Pre-test Seek reset failed: pos<%v>, %v", pos, seek_err) { + return + } + + reader: bufio.Reader + writer: bufio.Writer + read_writer: bufio.Read_Writer + + bufio.reader_init(&reader, stream) + defer bufio.reader_destroy(&reader) + bufio.writer_init(&writer, stream) + defer bufio.writer_destroy(&writer) + + bufio.read_writer_init(&read_writer, &reader, &writer) + + // os2 file stream proc close and destroy are the same. + results, _ := _test_stream(t, bufio.read_writer_to_stream(&read_writer), buf[:], do_destroy = false) + + log.debugf("%#v", results) +} diff --git a/tests/core/math/test_core_math.odin b/tests/core/math/test_core_math.odin index 2a752e366..8d51c9da0 100644 --- a/tests/core/math/test_core_math.odin +++ b/tests/core/math/test_core_math.odin @@ -2,6 +2,7 @@ package test_core_math import "core:math" +import "core:strconv" import "core:testing" @test @@ -1229,4 +1230,37 @@ test_large_tan :: proc(t: ^testing.T) { f2 := math.tan(vf[i] + large) testing.expectf(t, close(t, f1, f2), "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } -} \ No newline at end of file +} + +@test +test_count_digits :: proc(t: ^testing.T) { + _run_test :: proc(t: ^testing.T, $base: int) { + buf: [64]u8 + for n in 0..= out_band_size, "Sanity check failed, your test call is flawed! Make sure that num_bytes >= out_band_size!") + 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) + 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!", - ) + 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) + 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) + 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) + 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) + 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) +} + +@(test) +test_intentional_leaks :: proc(t: ^testing.T) { + testing.expect_leaks(t, intentionally_leaky_test, leak_verifier) +} + +// Not tagged with @(test) because it's run through `test_intentional_leaks` +intentionally_leaky_test :: proc(t: ^testing.T) { + a: [dynamic]int + // Intentional leak + append(&a, 42) + + // Intentional bad free + b := uintptr(&a[0]) + 42 + free(rawptr(b)) +} + +leak_verifier :: proc(t: ^testing.T, ta: ^mem.Tracking_Allocator) { + testing.expect_value(t, len(ta.allocation_map), 1) + testing.expect_value(t, len(ta.bad_free_array), 1) } \ No newline at end of file diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index a8f41082a..8a9272882 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -1,14 +1,17 @@ /* Copyright 2021 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. graphitemaster: pton/ntop IANA test vectors + Feoramund: FreeBSD-specific tests. A test suite for `core:net` */ -//+build !netbsd !freebsd !openbsd +//+build !netbsd +//+build !openbsd package test_core_net import "core:testing" diff --git a/tests/core/net/test_core_net_freebsd.odin b/tests/core/net/test_core_net_freebsd.odin new file mode 100644 index 000000000..61e801f2b --- /dev/null +++ b/tests/core/net/test_core_net_freebsd.odin @@ -0,0 +1,89 @@ +/* + Copyright 2021 Jeroen van Rijn . + Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + graphitemaster: pton/ntop IANA test vectors + Feoramund: FreeBSD-specific tests. + + A test suite for `core:net` +*/ +//+build freebsd +package test_core_net + +import "core:net" +import "core:time" +import "core:testing" + +ENDPOINT_DUPLICATE_BINDING := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 11000} +ENDPOINT_EPIPE_TEST := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 11001} + +@test +test_duplicate_binding :: proc(t: ^testing.T) { + // FreeBSD has the capacity to permit multiple processes and sockets to + // bind on the same port with the right option. + + raw_socket1, err_create1 := net.create_socket(.IP4, .TCP) + if !testing.expect_value(t, err_create1, nil) { + return + } + defer net.close(raw_socket1) + tcp_socket1 := raw_socket1.(net.TCP_Socket) + err_set1 := net.set_option(tcp_socket1, .Reuse_Port, true) + if !testing.expect_value(t, err_set1, nil) { + return + } + err_bind1 := net.bind(tcp_socket1, ENDPOINT_DUPLICATE_BINDING) + if !testing.expect_value(t, err_bind1, nil) { + return + } + + raw_socket2, err_create2 := net.create_socket(.IP4, .TCP) + if !testing.expect_value(t, err_create2, nil) { + return + } + defer net.close(raw_socket2) + tcp_socket2 := raw_socket2.(net.TCP_Socket) + err_set2 := net.set_option(tcp_socket2, .Reuse_Port, true) + if !testing.expect_value(t, err_set2, nil) { + return + } + err_bind2 := net.bind(tcp_socket2, ENDPOINT_DUPLICATE_BINDING) + if !testing.expect_value(t, err_bind2, nil) { + return + } +} + +@test +test_sigpipe_bypass :: proc(t: ^testing.T) { + // If the internals aren't working as expected, this test will fail by raising SIGPIPE. + + server_socket, listen_err := net.listen_tcp(ENDPOINT_EPIPE_TEST) + if !testing.expect_value(t, listen_err, nil) { + return + } + defer net.close(server_socket) + + client_socket, dial_err := net.dial_tcp(ENDPOINT_EPIPE_TEST) + if !testing.expect_value(t, dial_err, nil) { + return + } + defer net.close(client_socket) + + time.sleep(10 * time.Millisecond) + + net.close(server_socket) + + time.sleep(10 * time.Millisecond) + + data := "Hellope!" + bytes_written, err_send := net.send(client_socket, transmute([]u8)data) + if !testing.expect_value(t, err_send, net.TCP_Send_Error.Cannot_Send_More_Data) { + return + } + if !testing.expect_value(t, bytes_written, 0) { + return + } +} diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 8cd3b3917..6174f2d5c 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -9,6 +9,7 @@ download_assets :: proc() { } } +@(require) import "bytes" @(require) import "c/libc" @(require) import "compress" @(require) import "container" @@ -22,6 +23,7 @@ download_assets :: proc() { @(require) import "encoding/xml" @(require) import "flags" @(require) import "fmt" +@(require) import "io" @(require) import "math" @(require) import "math/big" @(require) import "math/linalg/glsl" @@ -37,9 +39,11 @@ download_assets :: proc() { @(require) import "slice" @(require) import "strconv" @(require) import "strings" +@(require) import "sys/posix" @(require) import "sys/windows" @(require) import "text/i18n" @(require) import "text/match" +@(require) import "text/regex" @(require) import "thread" @(require) import "time" @(require) import "unicode" diff --git a/tests/core/odin/test_file_tags.odin b/tests/core/odin/test_file_tags.odin new file mode 100644 index 000000000..4af1afc75 --- /dev/null +++ b/tests/core/odin/test_file_tags.odin @@ -0,0 +1,155 @@ +package test_core_odin_parser + +import "base:runtime" +import "core:testing" +import "core:slice" +import "core:odin/ast" +import "core:odin/parser" + +@test +test_parse_file_tags :: proc(t: ^testing.T) { + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + Test_Case :: struct { + src: string, + tags: parser.File_Tags, + matching_targets: []struct{ + target: parser.Build_Target, + result: bool, + }, + } + + test_cases := []Test_Case{ + {// [0] + src = ``, + tags = {}, + }, {// [1] + src = ` +package main + `, + tags = {}, + matching_targets = { + {{.Windows, .amd64, "foo"}, true}, + }, + }, {// [2] + src = ` +//+build linux, darwin, freebsd, openbsd, netbsd, haiku +//+build arm32, arm64 +package main + `, + tags = { + build = { + {os = {.Linux}, arch = runtime.ALL_ODIN_ARCH_TYPES}, + {os = {.Darwin}, arch = runtime.ALL_ODIN_ARCH_TYPES}, + {os = {.FreeBSD}, arch = runtime.ALL_ODIN_ARCH_TYPES}, + {os = {.OpenBSD}, arch = runtime.ALL_ODIN_ARCH_TYPES}, + {os = {.NetBSD}, arch = runtime.ALL_ODIN_ARCH_TYPES}, + {os = {.Haiku}, arch = runtime.ALL_ODIN_ARCH_TYPES}, + {os = runtime.ALL_ODIN_OS_TYPES, arch = {.arm32}}, + {os = runtime.ALL_ODIN_OS_TYPES, arch = {.arm64}}, + }, + }, + matching_targets = { + {{.Linux, .amd64, "foo"}, true}, + {{.Windows, .arm64, "foo"}, true}, + {{.Windows, .amd64, "foo"}, false}, + }, + }, {// [3] + src = ` +// +private +//+lazy +// +no-instrumentation +//+ignore +// some other comment +package main + `, + tags = { + private = .Package, + no_instrumentation = true, + lazy = true, + ignore = true, + }, + matching_targets = { + {{.Linux, .amd64, "foo"}, false}, + }, + }, {// [4] + src = ` +//+build-project-name foo !bar, baz +//+build js wasm32, js wasm64p32 +package main + `, + tags = { + build_project_name = {{"foo", "!bar"}, {"baz"}}, + build = { + { + os = {.JS}, + arch = {.wasm32}, + }, { + os = {.JS}, + arch = {.wasm64p32}, + }, + }, + }, + matching_targets = { + {{.JS, .wasm32, "foo"}, true}, + {{.JS, .wasm64p32, "baz"}, true}, + {{.JS, .wasm64p32, "bar"}, false}, + }, + }, + } + + for test_case, test_case_i in test_cases { + + file := ast.File{ + fullpath = "test.odin", + src = test_case.src, + } + + p := parser.default_parser() + ok := parser.parse_file(&p, &file) + + testing.expect(t, ok, "bad parse") + + tags := parser.parse_file_tags(file) + + + build_project_name_the_same: bool + check: if len(test_case.tags.build_project_name) == len(tags.build_project_name) { + for tag, i in test_case.tags.build_project_name { + slice.equal(tag, tags.build_project_name[i]) or_break check + } + build_project_name_the_same = true + } + testing.expectf(t, build_project_name_the_same, + "[%d] file_tags.build_project_name expected:\n%#v, got:\n%#v", + test_case_i, test_case.tags.build_project_name, tags.build_project_name) + + testing.expectf(t, slice.equal(test_case.tags.build, tags.build), + "[%d] file_tags.build expected:\n%#v, got:\n%#v", + test_case_i, test_case.tags.build, tags.build) + + testing.expectf(t, test_case.tags.private == tags.private, + "[%d] file_tags.private expected:\n%v, got:\n%v", + test_case_i, test_case.tags.private, tags.private) + + testing.expectf(t, test_case.tags.ignore == tags.ignore, + "[%d] file_tags.ignore expected:\n%v, got:\n%v", + test_case_i, test_case.tags.ignore, tags.ignore) + + testing.expectf(t, test_case.tags.lazy == tags.lazy, + "[%d] file_tags.lazy expected:\n%v, got:\n%v", + test_case_i, test_case.tags.lazy, tags.lazy) + + testing.expectf(t, test_case.tags.no_instrumentation == tags.no_instrumentation, + "[%d] file_tags.no_instrumentation expected:\n%v, got:\n%v", + test_case_i, test_case.tags.no_instrumentation, tags.no_instrumentation) + + for target in test_case.matching_targets { + matches := parser.match_build_tags(test_case.tags, target.target) + testing.expectf(t, matches == target.result, + "[%d] Expected parser.match_build_tags(%#v) == %v, got %v", + test_case_i, target.target, target.result, matches) + } + } +} diff --git a/tests/core/odin/test_parser.odin b/tests/core/odin/test_parser.odin index 772ae5982..b4310104f 100644 --- a/tests/core/odin/test_parser.odin +++ b/tests/core/odin/test_parser.odin @@ -1,8 +1,12 @@ package test_core_odin_parser +import "base:runtime" + +import "core:fmt" +import "core:log" import "core:odin/ast" import "core:odin/parser" -import "base:runtime" +import "core:odin/tokenizer" import "core:testing" @test @@ -34,7 +38,7 @@ Foo :: bit_field uint {} Foo :: bit_field uint {hello: bool | 1} Foo :: bit_field uint { - hello: bool | 1, + hello: bool | 1 ` + "`fmt:\"-\"`" + `, hello: bool | 5, } @@ -48,6 +52,17 @@ Foo :: bit_field uint { } p := parser.default_parser() + + p.err = proc(pos: tokenizer.Pos, format: string, args: ..any) { + message := fmt.tprintf(format, ..args) + log.errorf("%s(%d:%d): %s", pos.file, pos.line, pos.column, message) + } + + p.warn = proc(pos: tokenizer.Pos, format: string, args: ..any) { + message := fmt.tprintf(format, ..args) + log.warnf("%s(%d:%d): %s", pos.file, pos.line, pos.column, message) + } + ok := parser.parse_file(&p, &file) testing.expect(t, ok, "bad parse") -} \ No newline at end of file +} diff --git a/tests/core/os/dir/alink.txt b/tests/core/os/dir/alink.txt deleted file mode 120000 index 1891a26c0..000000000 --- a/tests/core/os/dir/alink.txt +++ /dev/null @@ -1 +0,0 @@ -./a.txt \ No newline at end of file diff --git a/tests/core/os/os.odin b/tests/core/os/os.odin index 97ceaeb1e..1510bad31 100644 --- a/tests/core/os/os.odin +++ b/tests/core/os/os.odin @@ -1,15 +1,40 @@ -package tests_core_os +package test_core_os +import "core:c/libc" +import win32 "core:sys/windows" import "core:os" import "core:slice" - import "core:testing" +import "core:log" + +_ :: libc +_ :: win32 @(test) read_dir :: proc(t: ^testing.T) { + when ODIN_OS == .Windows { + link := win32.utf8_to_wstring(#directory + "dir/alink.txt") + target := win32.utf8_to_wstring(#directory + "dir/a.txt") + sym_err := win32.CreateSymbolicLinkW(link, target, win32.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) + + if !sym_err { + log.infof("Unable to create symlink, skipping test. Error: %v", win32.GetLastError()) + return + } + } else { + sym_err := libc.system("ln -s " + #directory + "dir/a.txt " + #directory + "dir/alink.txt") + if sym_err != 0 { + log.infof("Unable to create symlink, skipping test. Error: %v", sym_err) + return + } + } + defer os.remove(#directory + "dir/alink.txt") + fd, err := os.open(#directory + "/dir") testing.expect_value(t, err, nil) - defer os.close(fd) + defer { + testing.expect_value(t, os.close(fd), nil) + } dir, err2 := os.read_dir(fd, -1) testing.expect_value(t, err2, nil) @@ -19,17 +44,20 @@ read_dir :: proc(t: ^testing.T) { 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") + if len(dir) > 0 { + 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") + } + } + if len(dir) > 1 { + testing.expect_value(t, dir[1].name, "b.txt") + } + if len(dir) > 2 { + testing.expect_value(t, dir[2].name, "sub") + testing.expect(t, dir[2].is_dir, "is not a directory") } - - 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/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 008146dcf..f39caff48 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -11,6 +11,7 @@ import "core:testing" test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) { arena: runtime.Arena context.allocator = runtime.arena_allocator(&arena) + defer runtime.arena_destroy(&arena) _, _ = mem.alloc(int(runtime.DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE)-120) _, err := mem.alloc(112, 32) @@ -22,6 +23,7 @@ test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) { test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) { arena: runtime.Arena context.allocator = runtime.arena_allocator(&arena) + defer runtime.arena_destroy(&arena) mappy: map[[8]int]int err := reserve(&mappy, 50000) @@ -32,6 +34,7 @@ test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) { test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) { arena: runtime.Arena context.allocator = runtime.arena_allocator(&arena) + defer runtime.arena_destroy(&arena) bytes, err := mem.alloc_bytes(10, 16) testing.expect(t, err == nil) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 3e249055b..16d5b23a4 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -275,4 +275,34 @@ test_unique :: proc(t: ^testing.T) { delete(original) delete(expected) } -} \ No newline at end of file +} + +@test +test_compare_empty :: proc(t: ^testing.T) { + a := []int{} + b := []int{} + c: [dynamic]int = { 0 } + d: [dynamic]int = { 1 } + clear(&c) + clear(&d) + defer { + delete(c) + delete(d) + } + + testing.expectf(t, len(a) == 0, + "Expected length of slice `a` to be zero") + testing.expectf(t, len(c) == 0, + "Expected length of dynamic array `c` to be zero") + testing.expectf(t, len(d) == 0, + "Expected length of dynamic array `d` to be zero") + + testing.expectf(t, slice.equal(a, a), + "Expected empty slice to be equal to itself") + testing.expectf(t, slice.equal(a, b), + "Expected two different but empty stack-based slices to be equivalent") + testing.expectf(t, slice.equal(a, c[:]), + "Expected empty slice to be equal to slice of empty dynamic array") + testing.expectf(t, slice.equal(c[:], d[:]), + "Expected two separate empty slices of two dynamic arrays to be equal") +} diff --git a/tests/core/strings/test_core_strings.odin b/tests/core/strings/test_core_strings.odin index 0ee2b3eb9..0d94b9c62 100644 --- a/tests/core/strings/test_core_strings.odin +++ b/tests/core/strings/test_core_strings.odin @@ -48,18 +48,19 @@ Cut_Test :: struct { } cut_tests :: []Cut_Test{ - {"some example text", 0, 4, "some" }, - {"some example text", 2, 2, "me" }, - {"some example text", 5, 7, "example" }, - {"some example text", 5, 0, "example text"}, - {"恥ずべきフクロウ", 4, 0, "フクロウ" }, + {"some example text", 0, 0, "some example text" }, + {"some example text", 0, 4, "some" }, + {"some example text", 2, 2, "me" }, + {"some example text", 5, 7, "example" }, + {"some example text", 5, 0, "example text" }, + {"恥ずべきフクロウ", 0, 0, "恥ずべきフクロウ" }, + {"恥ずべきフクロウ", 4, 0, "フクロウ" }, } @test test_cut :: proc(t: ^testing.T) { for test in cut_tests { res := strings.cut(test.input, test.offset, test.length) - defer delete(res) testing.expectf( t, @@ -123,4 +124,35 @@ test_case_conversion :: proc(t: ^testing.T) { testing.expectf(t, result == entry.s, "ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result) } } -} \ No newline at end of file +} + +@(test) +test_substring :: proc(t: ^testing.T) { + Case :: struct { + s: string, + start: int, + end: int, + sub: string, + ok: bool, + } + cases := []Case { + {ok = true}, + {s = "", start = -1, ok = false}, + {s = "", end = -1, ok = false}, + {s = "", end = +1, ok = false}, + {s = "Hello", end = len("Hello"), sub = "Hello", ok = true}, + {s = "Hello", start = 1, end = len("Hello"), sub = "ello", ok = true}, + {s = "Hello", start = 1, end = len("Hello") - 1, sub = "ell", ok = true}, + {s = "Hello", end = len("Hello") + 1, sub = "Hello", ok = false}, + {s = "小猫咪", start = 0, end = 3, sub = "小猫咪", ok = true}, + {s = "小猫咪", start = 1, end = 3, sub = "猫咪", ok = true}, + {s = "小猫咪", start = 1, end = 5, sub = "猫咪", ok = false}, + {s = "小猫咪", start = 1, end = 1, sub = "", ok = true}, + } + + for tc in cases { + sub, ok := strings.substring(tc.s, tc.start, tc.end) + testing.expectf(t, ok == tc.ok, "expected %v[%v:%v] to return ok: %v", tc.s, tc.start, tc.end, tc.ok) + testing.expectf(t, sub == tc.sub, "expected %v[%v:%v] to return sub: %v, got: %v", tc.s, tc.start, tc.end, tc.sub, sub) + } +} diff --git a/tests/core/sys/posix/posix.odin b/tests/core/sys/posix/posix.odin new file mode 100644 index 000000000..fa30d1601 --- /dev/null +++ b/tests/core/sys/posix/posix.odin @@ -0,0 +1,310 @@ +//+build darwin, freebsd, openbsd, netbsd +package tests_core_posix + +import "base:runtime" + +import "core:log" +import "core:path/filepath" +import "core:strings" +import "core:sync" +import "core:sys/posix" +import "core:testing" +import "core:time" + +@(test) +test_arpa_inet :: proc(t: ^testing.T) { + + check :: proc(t: ^testing.T, $af: posix.AF, src: cstring, expect: posix.pton_result, loc := #caller_location) { + when af == .INET { + addr: posix.in_addr + dst: [posix.INET_ADDRSTRLEN]byte + } else { + addr: posix.in6_addr + dst: [posix.INET6_ADDRSTRLEN]byte + } + + res := posix.inet_pton(af, src, &addr, size_of(addr)) + testing.expect_value(t, res, expect, loc) + + if expect == .SUCCESS { + back := posix.inet_ntop(af, &addr, raw_data(dst[:]), len(dst)) + testing.expect_value(t, back, src, loc) + + when af == .INET { + back = posix.inet_ntoa(addr) + testing.expect_value(t, back, src, loc) + } + } + } + + check(t, .INET, "127.0.0.1", .SUCCESS) + check(t, .INET, "blah", .INVALID) + check(t, .INET6, "::1", .SUCCESS) + check(t, .INET6, "L", .INVALID) + check(t, .UNIX, "127.0.0.1", .AFNOSUPPORT) +} + +@(test) +test_dirent :: proc(t: ^testing.T) { + test := #load_directory(#directory) + test_map: map[string]struct{} + defer delete(test_map) + + test_map[".."] = {} + test_map["."] = {} + + for file in test { + test_map[filepath.base(file.name)] = {} + } + + { + list: [^]^posix.dirent + ret := posix.scandir(#directory, &list) + testing.expectf(t, ret >= 0, "%v >= 0: %v", ret, posix.strerror(posix.errno())) + defer posix.free(list) + + entries := list[:ret] + for entry in entries { + defer posix.free(entry) + + if entry.d_type != .REG { + continue + } + + name := string(cstring(raw_data(entry.d_name[:]))) + testing.expectf(t, name in test_map, "%v in %v", name, test_map) + } + } + + { + dir := posix.opendir(#directory) + defer posix.closedir(dir) + + for { + posix.set_errno(.NONE) + entry := posix.readdir(dir) + if entry == nil { + testing.expect_value(t, posix.errno(), posix.Errno.NONE) + break + } + + if entry.d_type != .REG { + continue + } + + name := string(cstring(raw_data(entry.d_name[:]))) + testing.expectf(t, name in test_map, "%v in %v", name, test_map) + } + } +} + +@(test) +test_errno :: proc(t: ^testing.T) { + posix.errno(posix.Errno.ENOMEM) + testing.expect_value(t, posix.errno(), posix.Errno.ENOMEM) + + res := posix.open("", {}) + testing.expect_value(t, res, -1) + testing.expect_value(t, posix.errno(), posix.Errno.ENOENT) +} + +@(test) +test_fcntl :: proc(t: ^testing.T) { + res := posix.open(#file, { .WRONLY, .CREAT, .EXCL }) + testing.expect_value(t, res, -1) + testing.expect_value(t, posix.errno(), posix.Errno.EEXIST) +} + +@(test) +test_fnmatch :: proc(t: ^testing.T) { + testing.expect_value(t, posix.fnmatch("*.odin", #file, {}), 0) + testing.expect_value(t, posix.fnmatch("*.txt", #file, {}), posix.FNM_NOMATCH) + testing.expect_value(t, posix.fnmatch("**/*.odin", #file, {}), 0) +} + +@(test) +test_glob :: proc(t: ^testing.T) { + glob: posix.glob_t + res := posix.glob(#directory + ":)))))))", {}, nil, &glob) + testing.expect_value(t, res, posix.Glob_Result.NOMATCH) + posix.globfree(&glob) +} + +@(test) +test_langinfo :: proc(t: ^testing.T) { + locale := posix.setlocale(.TIME, nil) + testing.expectf(t, locale == "POSIX" || locale == "C", "invalid locale for test: %v", locale) + + day1 := posix.nl_langinfo(.DAY_1) + testing.expect_value(t, day1, "Sunday") +} + +@(test) +test_libgen :: proc(t: ^testing.T) { + tests := [][3]cstring{ + { "usr", ".", "usr" }, + { "usr/", ".", "usr" }, + { "", ".", "." }, + { "/", "/", "/" }, + { "//", "/", "/" }, + { "///", "/", "/" }, + { "/usr/", "/", "usr" }, + { "/usr/lib", "/usr", "lib" }, + { "//usr//lib//", "//usr", "lib" }, + { "/home//dwc//test", "/home//dwc", "test" }, + } + + for test in tests { + // NOTE: dir/basename can change their input so they can't be literals. + + dinput := strings.clone_to_cstring(string(test[0])) + defer delete(dinput) + + dir := posix.dirname(dinput) + testing.expectf(t, dir == test[1], "dirname(%q) == %q, expected %q", test[0], dir, test[1]) + + binput := strings.clone_to_cstring(string(test[0])) + defer delete(binput) + + base := posix.basename(binput) + testing.expectf(t, base == test[2], "basename(%q) == %q, expected %q", test[0], base, test[2]) + } +} + +@(test) +test_locale :: proc(t: ^testing.T) { + lconv := posix.localeconv() + testing.expect(t, lconv != nil) + + locale := posix.setlocale(.ALL, nil) + testing.expectf(t, locale == "POSIX" || locale == "C", "%q is not POSIX or C", locale) +} + +@(test) +test_monetary :: proc(t: ^testing.T) { + when ODIN_OS == .Darwin && .Address in ODIN_SANITIZER_FLAGS { + log.warn("skipping on darwin with -sanitize:address, this fails inside macOS (also from C/clang)") + return + } + + value := 123456.789 + buf: [128]byte + size := posix.strfmon(raw_data(buf[:]), len(buf), "%n", value) + testing.expectf(t, int(size) != -1, "strfmon failure: %v", posix.strerror(posix.errno())) + log.debug(string(buf[:size])) +} + +@(test) +test_stat :: proc(t: ^testing.T) { + stat: posix.stat_t + testing.expect_value(t, posix.stat(#file, &stat), posix.result.OK) + testing.expect(t, posix.S_ISREG(stat.st_mode)) + testing.expect_value(t, stat.st_mode, posix.mode_t{.IROTH, .IRGRP, .IRUSR, .IWUSR, .IFREG}) + + CONTENT := #load(#file) + testing.expect_value(t, stat.st_size, posix.off_t(len(CONTENT))) +} + +@(test) +test_termios :: proc(t: ^testing.T) { + testing.expect_value(t, transmute(posix.CControl_Flags)posix.tcflag_t(posix._CSIZE), posix.CSIZE) + + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._NLDLY), posix.NLDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._CRDLY), posix.CRDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._TABDLY), posix.TABDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._BSDLY), posix.BSDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._VTDLY), posix.VTDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._FFDLY), posix.FFDLY) +} + +@(test) +test_signal :: proc(t: ^testing.T) { + @static tt: ^testing.T + tt = t + + @static ctx: runtime.Context + ctx = context + + act: posix.sigaction_t + act.sa_flags = {.SIGINFO, .RESETHAND} + act.sa_sigaction = handler + testing.expect_value(t, posix.sigaction(.SIGCHLD, &act, nil), posix.result.OK) + + handler :: proc "c" (sig: posix.Signal, info: ^posix.siginfo_t, address: rawptr) { + context = ctx + testing.expect_value(tt, sig, posix.Signal.SIGCHLD) + testing.expect_value(tt, info.si_signo, posix.Signal.SIGCHLD) + testing.expect_value(tt, info.si_status, 69) + testing.expect_value(tt, info.si_code.chld, posix.CLD_Code.EXITED) + } + + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failure: %v", posix.strerror()) + case 0: + posix.exit(69) + case: + for { + status: i32 + res := posix.waitpid(pid, &status, {}) + if res == -1 { + if !testing.expect_value(t, posix.errno(), posix.Errno.EINTR) { + break + } + continue + } + + if posix.WIFEXITED(status) || posix.WIFSIGNALED(status) { + testing.expect(t, posix.WIFEXITED(status)) + testing.expect(t, posix.WEXITSTATUS(status) == 69) + break + } + } + } +} + +@(test) +test_pthreads :: proc(t: ^testing.T) { + testing.set_fail_timeout(t, time.Second) + + NTHREADS :: 3 + thread_ids: [NTHREADS]posix.pthread_t + + @static counter: int + + for &tid in thread_ids { + posix.pthread_create(&tid, nil, thread_function, nil) + } + + for tid in thread_ids { + posix.pthread_join(tid, nil) + } + + testing.expect_value(t, counter, NTHREADS) + + thread_function :: proc "c" (_: rawptr) -> rawptr { + sync.atomic_add(&counter, 1) + return nil + } +} + +@(test) +open_permissions :: proc(t: ^testing.T) { + in_mode := posix.mode_t{.IRUSR, .IWUSR, .IROTH, .IRGRP} + fd := posix.open("test_posix_permissions.txt", {.CREAT, .RDWR}, in_mode) + testing.expectf(t, fd != -1, "failed to open: %v", posix.strerror()) + + defer { + ret := posix.close(fd) + testing.expectf(t, ret == .OK, "failed to close: %v", posix.strerror()) + ret2 := posix.remove("test_posix_permissions.txt") + testing.expectf(t, ret2 == 0, "failed to remove: %v", posix.strerror()) + } + + stat: posix.stat_t + res := posix.fstat(fd, &stat) + testing.expectf(t, res == .OK, "failed to stat: %v", posix.strerror()) + + stat.st_mode -= posix.S_IFMT + testing.expect_value(t, stat.st_mode, in_mode) +} diff --git a/tests/core/sys/posix/structs.odin b/tests/core/sys/posix/structs.odin new file mode 100644 index 000000000..bdb1c24e3 --- /dev/null +++ b/tests/core/sys/posix/structs.odin @@ -0,0 +1,127 @@ +//+build darwin, freebsd, openbsd, netbsd +package tests_core_posix + +import "core:log" +import "core:testing" +import "core:sys/posix" + +// This test tests some of the process APIs of posix while also double checking size and alignment +// of the structs we bound. + +@(test) +execute_struct_checks :: proc(t: ^testing.T) { + log.debug("compiling C project") + { + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failed: %s", posix.strerror()) + case 0: + c_compiler := posix.getenv("CC") + if c_compiler == nil { + c_compiler = "clang" + } + + posix.execlp(c_compiler, + c_compiler, #directory + "/structs/structs.c", "-o", #directory + "/structs/c_structs", nil) + posix.exit(69) + case: + if !wait_for(t, pid) { return } + log.debug("C code has been compiled!") + } + } + + log.debug("compiling Odin project") + { + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failed: %s", posix.strerror()) + case 0: + posix.execlp(ODIN_ROOT + "/odin", + ODIN_ROOT + "/odin", "build", #directory + "/structs/structs.odin", "-out:" + #directory + "/structs/odin_structs", "-file", nil) + posix.exit(69) + case: + if !wait_for(t, pid) { return } + log.debug("Odin code has been compiled!") + } + } + + c_buf: [dynamic]byte + defer delete(c_buf) + c_out := get_output(t, &c_buf, #directory + "/structs/c_structs", nil) + + odin_buf: [dynamic]byte + defer delete(odin_buf) + odin_out := get_output(t, &odin_buf, #directory + "/structs/odin_structs", nil) + + testing.expectf(t, c_out == odin_out, "The C output and Odin output differ!\nC output:\n%s\n\n\n\nOdin Output:\n%s", c_out, odin_out) + + /* ----------- HELPERS ----------- */ + + wait_for :: proc(t: ^testing.T, pid: posix.pid_t) -> (ok: bool) { + log.debugf("waiting on pid %v", pid) + + waiting: for { + status: i32 + wpid := posix.waitpid(pid, &status, {}) + if !testing.expectf(t, wpid != -1, "waitpid() failure: %v", posix.strerror()) { + return false + } + + switch { + case posix.WIFEXITED(status): + ok = testing.expect_value(t, posix.WEXITSTATUS(status), 0) + break waiting + case posix.WIFSIGNALED(status): + log.errorf("child process raised: %v", posix.strsignal(posix.WTERMSIG(status))) + ok = false + break waiting + case: + log.errorf("unexpected status (this should never happen): %v", status) + ok = false + break waiting + } + } + + return + } + + get_output :: proc(t: ^testing.T, output: ^[dynamic]byte, cmd: ..cstring) -> (out_str: string) { + log.debugf("capturing output of: %v", cmd) + + pipe: [2]posix.FD + if !testing.expect_value(t, posix.pipe(&pipe), posix.result.OK) { + return + } + + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failed: %s", posix.strerror()) + return + case 0: + posix.close(pipe[0]) + posix.dup2(pipe[1], 1) + posix.execv(cmd[0], raw_data(cmd[:])) + panic(string(posix.strerror())) + case: + posix.close(pipe[1]) + log.debugf("waiting on pid %v", pid) + + reader: for { + buf: [256]byte + switch read := posix.read(pipe[0], &buf[0], 256); { + case read < 0: + log.errorf("read output failed: %v", posix.strerror()) + return + case read == 0: + break reader + case: + append(output, ..buf[:read]) + } + } + + wait_for(t, pid) + + return string(output[:]) + } + } +} diff --git a/tests/core/sys/posix/structs/.gitignore b/tests/core/sys/posix/structs/.gitignore new file mode 100644 index 000000000..646eae0a2 --- /dev/null +++ b/tests/core/sys/posix/structs/.gitignore @@ -0,0 +1,2 @@ +c_structs +odin_structs diff --git a/tests/core/sys/posix/structs/structs.c b/tests/core/sys/posix/structs/structs.c new file mode 100644 index 000000000..7d8038fbb --- /dev/null +++ b/tests/core/sys/posix/structs/structs.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + printf("dirent %zu %zu\n", sizeof(struct dirent), _Alignof(struct dirent)); + printf("flock %zu %zu\n", sizeof(struct flock), _Alignof(struct flock)); + printf("glob_t %zu %zu\n", sizeof(glob_t), _Alignof(glob_t)); + printf("group %zu %zu\n", sizeof(struct group), _Alignof(struct group)); + printf("lconv %zu %zu\n", sizeof(struct lconv), _Alignof(struct lconv)); + + printf("pthread_t %zu %zu\n", sizeof(pthread_t), _Alignof(pthread_t)); + printf("pthread_attr_t %zu %zu\n", sizeof(pthread_attr_t), _Alignof(pthread_attr_t)); + printf("pthread_key_t %zu %zu\n", sizeof(pthread_key_t), _Alignof(pthread_key_t)); + + printf("sched_param %zu %zu\n", sizeof(struct sched_param), _Alignof(struct sched_param)); + + printf("termios %zu %zu\n", sizeof(struct termios), _Alignof(struct termios)); + + printf("in_addr %zu %zu\n", sizeof(struct in_addr), _Alignof(struct in_addr)); + printf("in6_addr %zu %zu\n", sizeof(struct in6_addr), _Alignof(struct in6_addr)); + printf("sockaddr_in %zu %zu\n", sizeof(struct sockaddr_in), _Alignof(struct sockaddr_in)); + printf("sockaddr_in6 %zu %zu\n", sizeof(struct sockaddr_in6), _Alignof(struct sockaddr_in6)); + printf("ipv6_mreq %zu %zu\n", sizeof(struct ipv6_mreq), _Alignof(struct ipv6_mreq)); + + printf("sockaddr_storage %zu %zu\n", sizeof(struct sockaddr_storage), _Alignof(struct sockaddr_storage)); + printf("msghdr %zu %zu\n", sizeof(struct msghdr), _Alignof(struct msghdr)); + printf("cmsghdr %zu %zu\n", sizeof(struct cmsghdr), _Alignof(struct cmsghdr)); + printf("linger %zu %zu\n", sizeof(struct linger), _Alignof(struct linger)); + + printf("hostent %zu %zu\n", sizeof(struct hostent), _Alignof(struct hostent)); + printf("netent %zu %zu\n", sizeof(struct netent), _Alignof(struct netent)); + printf("protoent %zu %zu\n", sizeof(struct protoent), _Alignof(struct protoent)); + printf("servent %zu %zu\n", sizeof(struct servent), _Alignof(struct servent)); + printf("addrinfo %zu %zu\n", sizeof(struct addrinfo), _Alignof(struct addrinfo)); + + printf("pollfd %zu %zu\n", sizeof(struct pollfd), _Alignof(struct pollfd)); + + printf("passwd %zu %zu\n", sizeof(struct passwd), _Alignof(struct passwd)); + + printf("shmid_ds %zu %zu\n", sizeof(struct shmid_ds), _Alignof(struct shmid_ds)); + printf("ipc_perm %zu %zu\n", sizeof(struct ipc_perm), _Alignof(struct ipc_perm)); + printf("msqid_ds %zu %zu\n", sizeof(struct msqid_ds), _Alignof(struct msqid_ds)); + + printf("rlimit %zu %zu\n", sizeof(struct rlimit), _Alignof(struct rlimit)); + printf("rusage %zu %zu\n", sizeof(struct rusage), _Alignof(struct rusage)); + + printf("sockaddr_un %zu %zu\n", sizeof(struct sockaddr_un), _Alignof(struct sockaddr_un)); + + printf("utsname %zu %zu\n", sizeof(struct utsname), _Alignof(struct utsname)); + + printf("tms %zu %zu\n", sizeof(struct tms), _Alignof(struct tms)); + + printf("sigaction %zu %zu\n", sizeof(struct sigaction), _Alignof(struct sigaction)); + printf("stack_t %zu %zu\n", sizeof(stack_t), _Alignof(stack_t)); + printf("siginfo_t %zu %zu\n", sizeof(siginfo_t), _Alignof(siginfo_t)); + + printf("fd_set %zu %zu\n", sizeof(fd_set), _Alignof(fd_set)); + + printf("iovec %zu %zu\n", sizeof(struct iovec), _Alignof(struct iovec)); + + printf("semid_ds %zu %zu\n", sizeof(struct semid_ds), _Alignof(struct semid_ds)); + printf("sembuf %zu %zu\n", sizeof(struct sembuf), _Alignof(struct sembuf)); + + printf("itimerval %zu %zu\n", sizeof(struct itimerval), _Alignof(struct itimerval)); + + printf("utimbuf %zu %zu\n", sizeof(struct utimbuf), _Alignof(struct utimbuf)); + + printf("wordexp_t %zu %zu\n", sizeof(wordexp_t), _Alignof(wordexp_t)); + + printf("time_t %zu %zu\n", sizeof(time_t), _Alignof(time_t)); + printf("timespec %zu %zu\n", sizeof(struct timespec), _Alignof(struct timespec)); + printf("clock_t %zu %zu\n", sizeof(clock_t), _Alignof(clock_t)); + + return 0; +} diff --git a/tests/core/sys/posix/structs/structs.odin b/tests/core/sys/posix/structs/structs.odin new file mode 100644 index 000000000..2663d1e30 --- /dev/null +++ b/tests/core/sys/posix/structs/structs.odin @@ -0,0 +1,74 @@ +package main + +import "core:fmt" +import "core:sys/posix" + +main :: proc() { + fmt.println("dirent", size_of(posix.dirent), align_of(posix.dirent)) + fmt.println("flock", size_of(posix.flock), align_of(posix.flock)) + fmt.println("glob_t", size_of(posix.glob_t), align_of(posix.glob_t)) + fmt.println("group", size_of(posix.group), align_of(posix.group)) + fmt.println("lconv", size_of(posix.lconv), align_of(posix.lconv)) + + fmt.println("pthread_t", size_of(posix.pthread_t), align_of(posix.pthread_t)) + fmt.println("pthread_attr_t", size_of(posix.pthread_attr_t), align_of(posix.pthread_attr_t)) + fmt.println("pthread_key_t", size_of(posix.pthread_key_t), align_of(posix.pthread_key_t)) + + fmt.println("sched_param", size_of(posix.sched_param), align_of(posix.sched_param)) + + fmt.println("termios", size_of(posix.termios), align_of(posix.termios)) + + fmt.println("in_addr", size_of(posix.in_addr), align_of(posix.in_addr)) + fmt.println("in6_addr", size_of(posix.in6_addr), align_of(posix.in6_addr)) + fmt.println("sockaddr_in", size_of(posix.sockaddr_in), align_of(posix.sockaddr_in)) + fmt.println("sockaddr_in6", size_of(posix.sockaddr_in6), align_of(posix.sockaddr_in6)) + fmt.println("ipv6_mreq", size_of(posix.ipv6_mreq), align_of(posix.ipv6_mreq)) + + fmt.println("sockaddr_storage", size_of(posix.sockaddr_storage), align_of(posix.sockaddr_storage)) + fmt.println("msghdr", size_of(posix.msghdr), align_of(posix.msghdr)) + fmt.println("cmsghdr", size_of(posix.cmsghdr), align_of(posix.cmsghdr)) + fmt.println("linger", size_of(posix.linger), align_of(posix.linger)) + + fmt.println("hostent", size_of(posix.hostent), align_of(posix.hostent)) + fmt.println("netent", size_of(posix.netent), align_of(posix.netent)) + fmt.println("protoent", size_of(posix.protoent), align_of(posix.protoent)) + fmt.println("servent", size_of(posix.servent), align_of(posix.servent)) + fmt.println("addrinfo", size_of(posix.addrinfo), align_of(posix.addrinfo)) + + fmt.println("pollfd", size_of(posix.pollfd), align_of(posix.pollfd)) + fmt.println("passwd", size_of(posix.passwd), align_of(posix.passwd)) + + fmt.println("shmid_ds", size_of(posix.shmid_ds), align_of(posix.shmid_ds)) + fmt.println("ipc_perm", size_of(posix.ipc_perm), align_of(posix.ipc_perm)) + fmt.println("msqid_ds", size_of(posix.msqid_ds), align_of(posix.msqid_ds)) + + fmt.println("rlimit", size_of(posix.rlimit), align_of(posix.rlimit)) + fmt.println("rusage", size_of(posix.rusage), align_of(posix.rusage)) + + fmt.println("sockaddr_un", size_of(posix.sockaddr_un), align_of(posix.sockaddr_un)) + + fmt.println("utsname", size_of(posix.utsname), align_of(posix.utsname)) + + fmt.println("tms", size_of(posix.tms), align_of(posix.tms)) + + fmt.println("sigaction", size_of(posix.sigaction_t), align_of(posix.sigaction_t)) + fmt.println("stack_t", size_of(posix.stack_t), align_of(posix.stack_t)) + fmt.println("siginfo_t", size_of(posix.siginfo_t), align_of(posix.siginfo_t)) + + fmt.println("fd_set", size_of(posix.fd_set), align_of(posix.fd_set)) + + fmt.println("iovec", size_of(posix.iovec), align_of(posix.iovec)) + + fmt.println("semid_ds", size_of(posix.semid_ds), align_of(posix.semid_ds)) + fmt.println("sembuf", size_of(posix.sembuf), align_of(posix.sembuf)) + + fmt.println("itimerval", size_of(posix.itimerval), align_of(posix.itimerval)) + + fmt.println("utimbuf", size_of(posix.utimbuf), align_of(posix.utimbuf)) + + fmt.println("wordexp_t", size_of(posix.wordexp_t), align_of(posix.wordexp_t)) + + fmt.println("time_t", size_of(posix.time_t), align_of(posix.time_t)) + fmt.println("timespec", size_of(posix.timespec), align_of(posix.timespec)) + fmt.println("clock_t", size_of(posix.clock_t), align_of(posix.clock_t)) +} diff --git a/tests/core/sys/windows/test_clipboard.odin b/tests/core/sys/windows/test_clipboard.odin new file mode 100644 index 000000000..67fa6e4a2 --- /dev/null +++ b/tests/core/sys/windows/test_clipboard.odin @@ -0,0 +1,55 @@ +//+build windows +package test_core_sys_windows + +import "core:testing" +import win32 "core:sys/windows" +import "base:runtime" +import "core:strings" +import "core:mem" + +read_from_clipboard :: proc(wnd_handle: win32.HWND, allocator: runtime.Allocator) -> (result: string, ok: win32.BOOL) { + win32.IsClipboardFormatAvailable(win32.CF_TEXT) or_return + + win32.OpenClipboard(wnd_handle) or_return + defer win32.CloseClipboard() + + clipboard_data := win32.GetClipboardData(win32.CF_TEXT) + if clipboard_data != nil { + if cstr := cstring(win32.GlobalLock(win32.HGLOBAL(clipboard_data))); cstr != nil { + result = strings.clone_from_cstring(cstr, allocator) + ok = true + } + win32.GlobalUnlock(win32.HGLOBAL(clipboard_data)) + } + return +} + +write_to_clipboard :: proc(wnd_handle: win32.HWND, text: string) -> win32.BOOL { + win32.OpenClipboard(wnd_handle) or_return + defer win32.CloseClipboard() + win32.EmptyClipboard() + + h_mem := win32.HGLOBAL(win32.GlobalAlloc(win32.GMEM_MOVEABLE, len(text) + 1)) + if h_mem == nil {return false} + + cstr_dst := cast([^]u8)win32.GlobalLock(h_mem) + defer win32.GlobalUnlock(h_mem) + if cstr_dst == nil {return false} + + mem.copy(rawptr(cstr_dst), raw_data(text), len(text)) + cstr_dst[len(text)] = 0 + + win32.SetClipboardData(win32.CF_TEXT, win32.HANDLE(h_mem)) + return true +} + + +@(test) +verify_win32_clipboard :: proc(t: ^testing.T) { + ok1 := write_to_clipboard(nil, "Hello everynyan! OH MY GAH") + testing.expect_value(t, ok1, true) + + clipboard_content, ok2 := read_from_clipboard(nil, context.temp_allocator) + testing.expect_value(t, ok2, true) + testing.expect_value(t, clipboard_content, "Hello everynyan! OH MY GAH") +} diff --git a/tests/core/sys/windows/test_user32.odin b/tests/core/sys/windows/test_user32.odin new file mode 100644 index 000000000..0778fdf41 --- /dev/null +++ b/tests/core/sys/windows/test_user32.odin @@ -0,0 +1,13 @@ +//+build windows +package test_core_sys_windows + +import "core:testing" +import win32 "core:sys/windows" + +@(test) +verify_rawinput_code :: proc(t: ^testing.T) { + testing.expect_value(t, win32.GET_RAWINPUT_CODE_WPARAM(0), win32.RAWINPUT_CODE.RIM_INPUT) + testing.expect_value(t, win32.GET_RAWINPUT_CODE_WPARAM(1), win32.RAWINPUT_CODE.RIM_INPUTSINK) + testing.expect_value(t, win32.GET_RAWINPUT_CODE_WPARAM(0x100), win32.RAWINPUT_CODE.RIM_INPUT) + testing.expect_value(t, win32.GET_RAWINPUT_CODE_WPARAM(0x101), win32.RAWINPUT_CODE.RIM_INPUTSINK) +} diff --git a/tests/core/text/regex/test_core_text_regex.odin b/tests/core/text/regex/test_core_text_regex.odin new file mode 100644 index 000000000..dfc9224a8 --- /dev/null +++ b/tests/core/text/regex/test_core_text_regex.odin @@ -0,0 +1,1045 @@ +package test_core_text_regex + +import "core:fmt" +import "core:io" +import "core:log" +import "core:reflect" +import "core:strings" +import "core:testing" +import "core:text/regex" +import "core:text/regex/common" +import "core:text/regex/parser" +import "core:text/regex/tokenizer" + + +check_expression_with_flags :: proc(t: ^testing.T, pattern: string, flags: regex.Flags, haystack: string, needles: ..string, loc := #caller_location) { + rex, parse_err := regex.create(pattern, flags) + if !testing.expect_value(t, parse_err, nil, loc = loc) { + log.infof("Failed test's flags were: %v", flags, location = loc) + return + } + defer regex.destroy(rex) + + capture, success := regex.match(rex, haystack) + defer regex.destroy(capture) + + if len(needles) > 0 { + testing.expect(t, success, "match failed", loc = loc) + } + + matches_aligned := testing.expectf(t, len(needles) == len(capture.groups), + "expected %i match groups, got %i (flags: %w)", + len(needles), len(capture.groups), flags, loc = loc) + + if matches_aligned { + for needle, i in needles { + if !testing.expectf(t, capture.groups[i] == needle, + "match group %i was %q, expected %q (flags: %w)", + i, capture.groups[i], needle, flags, loc = loc) { + } + } + } else { + log.infof("match groups were: %v", capture.groups, location = loc) + } + + for pos, g in capture.pos { + pos_str := haystack[pos[0]:pos[1]] + if !testing.expectf(t, pos_str == capture.groups[g], "position string %v %q does not correspond to group string %q", pos, pos_str, capture.groups[g]) { + break + } + } +} + +check_expression :: proc(t: ^testing.T, pattern, haystack: string, needles: ..string, extra_flags := regex.Flags{}, loc := #caller_location) { + check_expression_with_flags(t, pattern, { .Global } + extra_flags, + haystack, ..needles, loc = loc) + check_expression_with_flags(t, pattern, { .Global, .No_Optimization } + extra_flags, + haystack, ..needles, loc = loc) + check_expression_with_flags(t, pattern, { .Global, .Unicode } + extra_flags, + haystack, ..needles, loc = loc) + check_expression_with_flags(t, pattern, { .Global, .Unicode, .No_Optimization } + extra_flags, + haystack, ..needles, loc = loc) +} + +expect_error :: proc(t: ^testing.T, pattern: string, expected_error: typeid, flags := regex.Flags{}, loc := #caller_location) { + rex, err := regex.create(pattern, flags) + regex.destroy(rex) + + variant := reflect.get_union_variant(err) + variant_ti := reflect.union_variant_type_info(variant) + expected_ti := type_info_of(expected_error) + + testing.expect_value(t, variant_ti, expected_ti, loc = loc) +} + + +@test +test_concatenation :: proc(t: ^testing.T) { + check_expression(t, "abc", "abc", "abc") +} + +@test +test_rune_class :: proc(t: ^testing.T) { + EXPR :: "[abc]" + check_expression(t, EXPR, "a", "a") + check_expression(t, EXPR, "b", "b") + check_expression(t, EXPR, "c", "c") +} + +@test +test_rune_ranges :: proc(t: ^testing.T) { + EXPR :: "0x[0-9A-Fa-f]+" + check_expression(t, EXPR, "0x0065c816", "0x0065c816") +} + +@test +test_rune_range_terminal_dash :: proc(t: ^testing.T) { + { + EXPR :: "[a-]" + check_expression(t, EXPR, "a", "a") + check_expression(t, EXPR, "-", "-") + } + { + EXPR :: "[-a]" + check_expression(t, EXPR, "a", "a") + check_expression(t, EXPR, "-", "-") + } + { + EXPR :: "[-a-]" + check_expression(t, EXPR, "a", "a") + check_expression(t, EXPR, "-", "-") + } + { + EXPR :: "[-]" + check_expression(t, EXPR, "-", "-") + } + { + EXPR :: "[--]" + check_expression(t, EXPR, "-", "-") + } + { + EXPR :: "[---]" + check_expression(t, EXPR, "-", "-") + } +} + +@test +test_rune_range_escaping_class :: proc(t: ^testing.T) { + { + EXPR :: `[\]a\[\.]` + check_expression(t, EXPR, "a", "a") + check_expression(t, EXPR, "[", "[") + check_expression(t, EXPR, "]", "]") + check_expression(t, EXPR, ".", ".") + check_expression(t, EXPR, "b") + } + { + EXPR :: `a[\\]b` + check_expression(t, EXPR, `a\b`, `a\b`) + } +} + +@test +test_negated_rune_class :: proc(t: ^testing.T) { + EXPR :: "[^ac-d]" + check_expression(t, EXPR, "a") + check_expression(t, EXPR, "b", "b") + check_expression(t, EXPR, "e", "e") + check_expression(t, EXPR, "c") + check_expression(t, EXPR, "d") +} + +@test +test_shorthand_classes :: proc(t: ^testing.T) { + EXPR_P :: `\d\w\s` + check_expression(t, EXPR_P, "1a ", "1a ") + check_expression(t, EXPR_P, "a!1") + EXPR_N :: `\D\W\S` + check_expression(t, EXPR_N, "a!1", "a!1") + check_expression(t, EXPR_N, "1a ") +} + +@test +test_shorthand_classes_in_classes :: proc(t: ^testing.T) { + EXPR_P :: `[\d][\w][\s]` + check_expression(t, EXPR_P, "1a ", "1a ") + check_expression(t, EXPR_P, "a!1") + EXPR_NP :: `[^\d][^\w][^\s]` + check_expression(t, EXPR_NP, "a!1", "a!1") + check_expression(t, EXPR_NP, "1a ") + EXPR_N :: `[\D][\W][\S]` + check_expression(t, EXPR_N, "a!1", "a!1") + check_expression(t, EXPR_N, "1a ") + EXPR_NN :: `[^\D][^\W][^\S]` + check_expression(t, EXPR_NN, "1a ", "1a ") + check_expression(t, EXPR_NN, "a!1") +} + +@test +test_mixed_shorthand_class :: proc(t: ^testing.T) { + EXPR_P :: `[\d\s]+` + check_expression(t, EXPR_P, "0123456789 98", "0123456789 98") + check_expression(t, EXPR_P, "!@#$%^&*()_()") + EXPR_NP :: `[^\d\s]+` + check_expression(t, EXPR_NP, "!@#$%^&*()_()", "!@#$%^&*()_()") + check_expression(t, EXPR_NP, "0123456789 98") +} + +@test +test_wildcard :: proc(t: ^testing.T) { + EXPR :: "." + check_expression(t, EXPR, "a", "a") + check_expression(t, EXPR, ".", ".") +} + +@test +test_alternation :: proc(t: ^testing.T) { + EXPR :: "aa|bb|cc" + check_expression(t, EXPR, "aa", "aa") + check_expression(t, EXPR, "bb", "bb") + check_expression(t, EXPR, "cc", "cc") +} + +@test +test_optional :: proc(t: ^testing.T) { + EXPR :: "a?a?a?aaa" + check_expression(t, EXPR, "aaa", "aaa") +} + +@test +test_repeat_zero :: proc(t: ^testing.T) { + EXPR :: "a*b" + check_expression(t, EXPR, "aaab", "aaab") +} + +@test +test_repeat_one :: proc(t: ^testing.T) { + EXPR :: "a+b" + check_expression(t, EXPR, "aaab", "aaab") +} + +@test +test_greedy :: proc(t: ^testing.T) { + HTML :: "" + + check_expression(t, "<.+>", HTML, HTML) + check_expression(t, "<.*>", HTML, HTML) + + check_expression(t, "aaa?", "aaa", "aaa") +} + +@test +test_non_greedy :: proc(t: ^testing.T) { + HTML :: "" + + check_expression(t, "<.+?>", HTML, "") + check_expression(t, "<.*?>", HTML, "") + + // NOTE: make a comment about optional non-greedy capture groups + check_expression(t, "aaa??", "aaa", "aa") +} + +@test +test_groups :: proc(t: ^testing.T) { + check_expression(t, "a(b)", "ab", /*|*/ "ab", "b") + check_expression(t, "(a)b", "ab", /*|*/ "ab", "a") + check_expression(t, "(a)(b)", "ab", /*|*/ "ab", "a", "b") + + check_expression(t, "(a(b))", "ab", /*|*/ "ab", "ab", "b") + check_expression(t, "((ab))", "ab", /*|*/ "ab", "ab", "ab") + check_expression(t, "((a)b)", "ab", /*|*/ "ab", "ab", "a") + + check_expression(t, "(ab)+", "ababababab", /*|*/ "ababababab", "ab") + check_expression(t, "((ab)+)", "ababababab", /*|*/ "ababababab", "ababababab", "ab") +} + +@test +test_class_group_repeat :: proc(t: ^testing.T) { + EXPR_1 :: "([0-9]:?)+" + EXPR_2 :: "([0-9]+:?)+" + check_expression(t, EXPR_1, "123:456:789", "123:456:789", "9") + check_expression(t, EXPR_2, "123:456:789", "123:456:789", "789") +} + +@test +test_non_capture_group :: proc(t: ^testing.T) { + EXPR :: "(?:a|b)c" + check_expression(t, EXPR, "ac", "ac") + check_expression(t, EXPR, "bc", "bc") + check_expression(t, EXPR, "cc") +} + +@test +test_optional_capture_group :: proc(t: ^testing.T) { + EXPR :: "^(blue|straw)?berry" + check_expression(t, EXPR, "berry", "berry") + check_expression(t, EXPR, "blueberry", "blueberry", "blue") + check_expression(t, EXPR, "strawberry", "strawberry", "straw") + check_expression(t, EXPR, "cranberry") +} + +@test +test_max_capture_groups :: proc(t: ^testing.T) { + sb_pattern := strings.builder_make() + sb_haystack := strings.builder_make() + expected_captures: [dynamic]string + defer { + strings.builder_destroy(&sb_pattern) + strings.builder_destroy(&sb_haystack) + delete(expected_captures) + } + + w_pattern := strings.to_writer(&sb_pattern) + w_haystack := strings.to_writer(&sb_haystack) + + // The full expression capture, capture 0: + for i in 1.. failed to convert to DateTime", + n) or_continue + + z, dtt_err := time.datetime_to_time(y) + testing.expectf(t, dtt_err, + "DateTime<%v> failed to convert to Time", + y) or_continue + + testing.expectf(t, x == z, + "Roundtrip conversion of Time to DateTime and back failed: got %v, expected %v", + z, x) + } +} + MONTH_DAYS := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} YEAR_START :: 1900 YEAR_END :: 2024 diff --git a/tests/core/unicode/test_core_unicode.odin b/tests/core/unicode/test_core_unicode.odin index a1f6ac934..30a40b30b 100644 --- a/tests/core/unicode/test_core_unicode.odin +++ b/tests/core/unicode/test_core_unicode.odin @@ -16,8 +16,7 @@ run_test_cases :: proc(t: ^testing.T, test_cases: []Test_Case, loc := #caller_lo result, _, _ := utf8.grapheme_count(c.str) if !testing.expectf(t, result == c.expected_clusters, "(#% 4i) graphemes: %i != %i, %q %s", i, result, c.expected_clusters, c.str, c.str, - loc = loc) - { + loc = loc) { failed += 1 } } diff --git a/tests/internal/test_union_switch.odin b/tests/internal/test_union_switch.odin new file mode 100644 index 000000000..f96c0e55e --- /dev/null +++ b/tests/internal/test_union_switch.odin @@ -0,0 +1,24 @@ +package test_internal + +import "core:log" +import "core:testing" + +@(test) +test_internal_pointer_union_switch :: proc(t: ^testing.T) { + foo: Maybe(^int) + + switch _ in foo { + case ^int: + log.error("incorrect case") + case nil: + } + + v := 1 + foo = &v + + switch _ in foo { + case ^int: + case nil: + log.error("incorrect case") + } +} diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 299e08791..dcea3d483 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -15,6 +15,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style ..\..\..\odin test ..\test_issue_2615.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_2637.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_2666.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_4210.odin %COMMON% || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 8b4c1e7f2..c3bc00e24 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -16,6 +16,7 @@ $ODIN test ../test_issue_2466.odin $COMMON $ODIN test ../test_issue_2615.odin $COMMON $ODIN test ../test_issue_2637.odin $COMMON $ODIN test ../test_issue_2666.odin $COMMON +$ODIN test ../test_issue_4210.odin $COMMON if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_4210.odin b/tests/issues/test_issue_4210.odin new file mode 100644 index 000000000..f50086a4e --- /dev/null +++ b/tests/issues/test_issue_4210.odin @@ -0,0 +1,85 @@ +// Tests issue #4210 https://github.com/odin-lang/Odin/issues/4210 +package test_issues + +import "core:testing" +import "base:intrinsics" + +@test +test_row_major_matrix :: proc(t: ^testing.T) { + row_major34: #row_major matrix[3,4]int = { + 11,12,13,14, + 21,22,23,24, + 31,32,33,34, + } + row_major34_expected := [?]int{11,12,13,14, 21,22,23,24, 31,32,33,34} + + row_major43: #row_major matrix[4,3]int = { + 11,12,13, + 21,22,23, + 31,32,33, + 41,42,43, + } + row_major43_expected := [?]int{11,12,13, 21,22,23, 31,32,33, 41,42,43} + + major34_flattened := intrinsics.matrix_flatten(row_major34) + major34_from_ptr := intrinsics.unaligned_load((^[3 * 4]int)(&row_major34)) + + for row in 0..<3 { + for column in 0..<4 { + idx := row * 4 + column + testing.expect_value(t, major34_flattened[idx], row_major34_expected[idx]) + testing.expect_value(t, major34_from_ptr [idx], row_major34_expected[idx]) + } + } + + major43_flattened := intrinsics.matrix_flatten(row_major43) + major43_from_ptr := intrinsics.unaligned_load((^[4 * 3]int)(&row_major43)) + + for row in 0..<4 { + for column in 0..<3 { + idx := row * 3 + column + testing.expect_value(t, major43_flattened[idx], row_major43_expected[idx]) + testing.expect_value(t, major43_from_ptr [idx], row_major43_expected[idx]) + } + } +} + +@test +test_row_minor_matrix :: proc(t: ^testing.T) { + row_minor34: matrix[3,4]int = { + 11,12,13,14, + 21,22,23,24, + 31,32,33,34, + } + row_minor34_expected := [?]int{11,21,31, 12,22,32, 13,23,33, 14,24,34} + + row_minor43: matrix[4,3]int = { + 11,12,13, + 21,22,23, + 31,32,33, + 41,42,43, + } + row_minor43_expected := [?]int{11,21,31,41, 12,22,32,42, 13,23,33,43} + + minor34_flattened := intrinsics.matrix_flatten(row_minor34) + minor34_from_ptr := intrinsics.unaligned_load((^[3 * 4]int)(&row_minor34)) + + for row in 0..<3 { + for column in 0..<4 { + idx := row * 4 + column + testing.expect_value(t, minor34_flattened[idx], row_minor34_expected[idx]) + testing.expect_value(t, minor34_from_ptr [idx], row_minor34_expected[idx]) + } + } + + minor43_flattened := intrinsics.matrix_flatten(row_minor43) + minor43_from_ptr := intrinsics.unaligned_load((^[4 * 3]int)(&row_minor43)) + + for row in 0..<4 { + for column in 0..<3 { + idx := row * 3 + column + testing.expect_value(t, minor43_flattened[idx], row_minor43_expected[idx]) + testing.expect_value(t, minor43_from_ptr [idx], row_minor43_expected[idx]) + } + } +} \ No newline at end of file diff --git a/tests/vendor/all.odin b/tests/vendor/all.odin index 1abbc5d7f..ba5628252 100644 --- a/tests/vendor/all.odin +++ b/tests/vendor/all.odin @@ -1,4 +1,4 @@ package tests_vendor -@(require) import "glfw" -@(require) import "lua/5.4" \ No newline at end of file +@(require) import "glfw" +@(require) import _ "lua/5.4" diff --git a/vendor/ENet/enet.odin b/vendor/ENet/enet.odin index d6278762e..b4ef3fe70 100644 --- a/vendor/ENet/enet.odin +++ b/vendor/ENet/enet.odin @@ -22,17 +22,17 @@ VERSION_MAJOR :: u8(1) VERSION_MINOR :: u8(3) VERSION_PATCH :: u8(17) -VERSION_CREATE :: #force_inline proc(major, minor, patch: u8) -> u32 { +VERSION_CREATE :: #force_inline proc "contextless" (major, minor, patch: u8) -> u32 { return (u32(major) << 16) | (u32(minor) << 8) | u32(patch) } -VERSION_GET_MAJOR :: #force_inline proc(version: u32) -> u8 { +VERSION_GET_MAJOR :: #force_inline proc "contextless" (version: u32) -> u8 { return u8((version >> 16) & 0xff) } -VERSION_GET_MINOR :: #force_inline proc(version: u32) -> u8 { +VERSION_GET_MINOR :: #force_inline proc "contextless" (version: u32) -> u8 { return u8((version >> 8) & 0xff) } -VERSION_GET_PATCH :: #force_inline proc(version: u32) -> u8 { +VERSION_GET_PATCH :: #force_inline proc "contextless" (version: u32) -> u8 { return u8(version & 0xff) } @@ -44,19 +44,19 @@ VERSION :: (u32(VERSION_MAJOR) << 16) | (u32(VERSION_MINOR) << 8) | u32(VERSION_ // Network byte order is always Big Endian. Instead of using the method ENet // uses (leveraging {n,h}to{n,h}{s,l}), we can just use Odin's endianess types // to get the correct byte swaps, if any. -HOST_TO_NET_16 :: #force_inline proc(value: u16) -> u16 { +HOST_TO_NET_16 :: #force_inline proc "contextless" (value: u16) -> u16 { return transmute(u16)u16be(value) } -HOST_TO_NET_32 :: #force_inline proc(value: u32) -> u32 { +HOST_TO_NET_32 :: #force_inline proc "contextless" (value: u32) -> u32 { return transmute(u32)u32be(value) } -NET_TO_HOST_16 :: #force_inline proc(value: u16) -> u16 { +NET_TO_HOST_16 :: #force_inline proc "contextless" (value: u16) -> u16 { return u16(transmute(u16be)value) } -NET_TO_HOST_32 :: #force_inline proc(value: u32) -> u32 { +NET_TO_HOST_32 :: #force_inline proc "contextless" (value: u32) -> u32 { return u32(transmute(u32be)value) } @@ -115,7 +115,7 @@ PacketFreeCallback :: proc "c" (packet: ^Packet) Packet :: struct { referenceCount: uint, flags: u32, - data: [^]u8, + data: [^]u8 `fmt:"v,dataLength"`, dataLength: uint, freeCallback: PacketFreeCallback, userData: rawptr, @@ -148,7 +148,7 @@ IncomingCommand :: struct { command: Protocol, fragmentCount: u32, fragmentsRemaining: u32, - fragments: [^]u32, + fragments: [^]u32 `fmt:"v,fragmentCount"`, packet: ^Packet, } @@ -221,7 +221,7 @@ Peer :: struct { address: Address, data: rawptr, state: PeerState, - channels: [^]Channel, + channels: [^]Channel `fmt:"v,channelCount"`, channelCount: uint, incomingBandwidth: u32, outgoingBandwidth: u32, @@ -292,7 +292,7 @@ Host :: struct { mtu: u32, randomSeed: u32, recalculateBandwidthLimits: i32, - peers: [^]Peer, + peers: [^]Peer `fmt:"v,peerCount"`, peerCount: uint, channelLimit: uint, serviceTime: u32, @@ -308,7 +308,7 @@ Host :: struct { compressor: Compressor, packetData: [2][PROTOCOL_MAXIMUM_MTU]u8, receivedAddress: Address, - receivedData: [^]u8, + receivedData: [^]u8 `fmt:"v,receivedDataLength"`, receivedDataLength: uint, totalSentData: u32, totalSentPackets: u32, diff --git a/vendor/ENet/time.odin b/vendor/ENet/time.odin index 87cef0bec..4b2d771a9 100644 --- a/vendor/ENet/time.odin +++ b/vendor/ENet/time.odin @@ -2,22 +2,22 @@ package ENet TIME_OVERFLOW :: u32(86400000) -TIME_LESS :: #force_inline proc(a, b: u32) -> bool { +TIME_LESS :: #force_inline proc "contextless" (a, b: u32) -> bool { return a - b >= TIME_OVERFLOW } -TIME_GREATER :: #force_inline proc(a, b: u32) -> bool { +TIME_GREATER :: #force_inline proc "contextless" (a, b: u32) -> bool { return b - a >= TIME_OVERFLOW } -TIME_LESS_EQUAL :: #force_inline proc(a, b: u32) -> bool { +TIME_LESS_EQUAL :: #force_inline proc "contextless" (a, b: u32) -> bool { return !TIME_GREATER(a, b) } -TIME_GREATER_EQUAL :: #force_inline proc(a, b: u32) -> bool { +TIME_GREATER_EQUAL :: #force_inline proc "contextless" (a, b: u32) -> bool { return TIME_LESS(a, b) } -TIME_DIFFERENCE :: #force_inline proc(a, b: u32) -> u32 { +TIME_DIFFERENCE :: #force_inline proc "contextless" (a, b: u32) -> u32 { return a - b >= TIME_OVERFLOW ? b - a : a - b } \ No newline at end of file diff --git a/vendor/ENet/unix.odin b/vendor/ENet/unix.odin index 0e3399aeb..1cbda5974 100644 --- a/vendor/ENet/unix.odin +++ b/vendor/ENet/unix.odin @@ -12,21 +12,21 @@ import "core:c" fds_bits: [FD_SETSIZE / 8 / size_of(c.long)]c.ulong, } -@(private="file") FD_ZERO :: #force_inline proc(s: ^fd_set) { +@(private="file") FD_ZERO :: #force_inline proc "contextless" (s: ^fd_set) { for i := size_of(fd_set) / size_of(c.long); i != 0; i -= 1 { s.fds_bits[i] = 0 } } -@(private="file") FD_SET :: #force_inline proc(d: i32, s: ^fd_set) { +@(private="file") FD_SET :: #force_inline proc "contextless" (d: i32, s: ^fd_set) { s.fds_bits[d / (8 * size_of(c.long))] |= c.ulong(1) << (c.ulong(d) % (8 * size_of(c.ulong))) } -@(private="file") FD_CLR :: #force_inline proc(d: i32, s: ^fd_set) { +@(private="file") FD_CLR :: #force_inline proc "contextless" (d: i32, s: ^fd_set) { s.fds_bits[d / (8 * size_of(c.long))] &~= c.ulong(1) << (c.ulong(d) % (8 * size_of(c.ulong))) } -@(private="file") FD_ISSET :: #force_inline proc(d: i32, s: ^fd_set) -> bool { +@(private="file") FD_ISSET :: #force_inline proc "contextless" (d: i32, s: ^fd_set) -> bool { return (s.fds_bits[d / (8 * size_of(c.long))] & c.ulong(1) << (c.ulong(d) % (8 * size_of(c.ulong)))) != 0 } // } @@ -42,18 +42,18 @@ Buffer :: struct { SocketSet :: distinct fd_set -SOCKETSET_EMPTY :: #force_inline proc(sockset: ^SocketSet) { +SOCKETSET_EMPTY :: #force_inline proc "contextless" (sockset: ^SocketSet) { FD_ZERO(cast(^fd_set)sockset) } -SOCKETSET_ADD :: #force_inline proc(sockset: ^SocketSet, socket: Socket) { +SOCKETSET_ADD :: #force_inline proc "contextless" (sockset: ^SocketSet, socket: Socket) { FD_SET(i32(socket), cast(^fd_set)sockset) } -SOCKETSET_REMOVE :: #force_inline proc(sockset: ^SocketSet, socket: Socket) { +SOCKETSET_REMOVE :: #force_inline proc "contextless" (sockset: ^SocketSet, socket: Socket) { FD_CLR(i32(socket), cast(^fd_set)sockset) } -SOCKSET_CHECK :: #force_inline proc(sockset: ^SocketSet, socket: Socket) -> bool { +SOCKSET_CHECK :: #force_inline proc "contextless" (sockset: ^SocketSet, socket: Socket) -> bool { return FD_ISSET(i32(socket), cast(^fd_set)sockset) } diff --git a/vendor/ENet/win32.odin b/vendor/ENet/win32.odin index d05cf5d99..0a1997ff9 100644 --- a/vendor/ENet/win32.odin +++ b/vendor/ENet/win32.odin @@ -20,7 +20,7 @@ foreign WinSock2 { fd_array: [FD_SETSIZE]SOCKET, } -@(private="file") FD_CLR :: proc(fd: SOCKET, s: ^fd_set) { +@(private="file") FD_CLR :: proc "contextless" (fd: SOCKET, s: ^fd_set) { for i := u32(0); i < s.fd_count; i += 1 { if s.fd_array[i] == fd { for i < s.fd_count - 1 { @@ -33,7 +33,7 @@ foreign WinSock2 { } } -@(private="file") FD_SET :: proc(fd: SOCKET, s: ^fd_set) { +@(private="file") FD_SET :: proc "contextless" (fd: SOCKET, s: ^fd_set) { for i := u32(0); i < s.fd_count; i += 1 { if s.fd_array[i] == fd { return @@ -46,11 +46,11 @@ foreign WinSock2 { s.fd_count += 1 } -@(private="file") FD_ZERO :: #force_inline proc (s: ^fd_set) { +@(private="file") FD_ZERO :: #force_inline proc "contextless" (s: ^fd_set) { s.fd_count = 0 } -@(private="file") FD_ISSET :: #force_inline proc (fd: SOCKET, s: ^fd_set) -> bool { +@(private="file") FD_ISSET :: #force_inline proc "contextless" (fd: SOCKET, s: ^fd_set) -> bool { return __WSAFDIsSet(fd, s) != 0 } // } @@ -66,18 +66,18 @@ Buffer :: struct { SocketSet :: distinct fd_set -SOCKETSET_EMPTY :: #force_inline proc(sockset: ^SocketSet) { +SOCKETSET_EMPTY :: #force_inline proc "contextless" (sockset: ^SocketSet) { FD_ZERO(cast(^fd_set)sockset) } -SOCKETSET_ADD :: #force_inline proc(sockset: ^SocketSet, socket: Socket) { +SOCKETSET_ADD :: #force_inline proc "contextless" (sockset: ^SocketSet, socket: Socket) { FD_SET(SOCKET(socket), cast(^fd_set)sockset) } -SOCKETSET_REMOVE :: #force_inline proc(sockset: ^SocketSet, socket: Socket) { +SOCKETSET_REMOVE :: #force_inline proc "contextless" (sockset: ^SocketSet, socket: Socket) { FD_CLR(SOCKET(socket), cast(^fd_set)sockset) } -SOCKSET_CHECK :: #force_inline proc(sockset: ^SocketSet, socket: Socket) -> bool { +SOCKSET_CHECK :: #force_inline proc "contextless" (sockset: ^SocketSet, socket: Socket) -> bool { return FD_ISSET(SOCKET(socket), cast(^fd_set)sockset) } \ No newline at end of file diff --git a/vendor/box2d/LICENSE b/vendor/box2d/LICENSE new file mode 100644 index 000000000..e90f787a5 --- /dev/null +++ b/vendor/box2d/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Erin Catto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/box2d/README.md b/vendor/box2d/README.md new file mode 100644 index 000000000..8b215aca4 --- /dev/null +++ b/vendor/box2d/README.md @@ -0,0 +1,32 @@ +![Box2D Logo](https://box2d.org/images/logo.svg) + +# Status +[![Build Status](https://github.com/erincatto/box2c/actions/workflows/build.yml/badge.svg)](https://github.com/erincatto/box2c/actions) + +# Box2D v3.0 Notes +This repository is beta and ready for testing. It should build on recent versions of clang and gcc. However, you will need the latest Visual Studio version for C11 atomics to compile (17.8.3+). + +AVX2 CPU support is assumed. You can turn this off in the CMake options and use SSE2 instead. + +# Box2D +Box2D is a 2D physics engine for games. + +## Contributing +Please do not submit pull requests with new features or core library changes. Instead, please file an issue first for discussion. For bugs, I prefer detailed bug reports over pull requests. + +# Giving Feedback +Please visit the discussions tab, file an issue, or start a chat on discord. + +## Community +- [Discord](https://discord.gg/NKYgCBP) + +## License +Box2D is developed by Erin Catto, and uses the [MIT license](https://en.wikipedia.org/wiki/MIT_License). + +## Sponsorship +Support development of Box2D through [Github Sponsors](https://github.com/sponsors/erincatto) + +## Ports, wrappers, and Bindings +- https://github.com/odin-lang/Odin/tree/master/vendor/box2d +- https://github.com/EnokViking/Box2DBeef +- https://github.com/HolyBlackCat/box2cpp \ No newline at end of file diff --git a/vendor/box2d/box2d.odin b/vendor/box2d/box2d.odin new file mode 100644 index 000000000..081e0861b --- /dev/null +++ b/vendor/box2d/box2d.odin @@ -0,0 +1,1523 @@ +package vendor_box2d + +import "base:intrinsics" +import "core:c" + +@(private) VECTOR_EXT :: "avx2" when #config(VENDOR_BOX2D_ENABLE_AVX2, intrinsics.has_target_feature("avx2")) else "sse2" + +when ODIN_OS == .Windows { + @(private) LIB_PATH :: "lib/box2d_windows_amd64_" + VECTOR_EXT + ".lib" +} else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { + @(private) LIB_PATH :: "lib/box2d_darwin_arm64.a" +} else when ODIN_OS == .Darwin { + @(private) LIB_PATH :: "lib/box2d_darwin_amd64_" + VECTOR_EXT + ".a" +} else when ODIN_ARCH == .amd64 { + @(private) LIB_PATH :: "lib/box2d_other_amd64_" + VECTOR_EXT + ".a" +} else { + @(private) LIB_PATH :: "lib/box2d_other.a" +} + +when !#exists(LIB_PATH) { + #panic("Could not find the compiled box2d libraries at \"" + LIB_PATH + "\", they can be compiled by running the `build.sh` script at `" + ODIN_ROOT + "vendor/box2d/build_box2d.sh\"`") +} + +foreign import lib { + LIB_PATH, +} + + +// Prototype for user allocation function +// @param size the allocation size in bytes +// @param alignment the required alignment, guaranteed to be a power of 2 +AllocFcn :: #type proc "c" (size: u32, alignment: i32) -> rawptr + +// Prototype for user free function +// @param mem the memory previously allocated through `b2AllocFcn` +FreeFcn :: #type proc "c" (mem: rawptr) + +// Prototype for the user assert callback. Return 0 to skip the debugger break. +AssertFcn :: #type proc "c" (condition, file_name: cstring, line_number: i32) -> i32 + +// Version numbering scheme. +// +// See https://semver.org/ +Version :: struct { + major: i32, // Significant changes + minor: i32, // Incremental changes + revision: i32, // Bug fixes +} + +when ODIN_OS == .Windows { + // Timer for profiling. This has platform specific code and may + // not work on every platform. + Timer :: struct { + start: i64, + } +} else when ODIN_OS == .Linux || ODIN_OS == .Darwin { + // Timer for profiling. This has platform specific code and may + // not work on every platform. + Timer :: struct { + start_sec: u64, + start_usec: u64, + } +} else { + // Timer for profiling. This has platform specific code and may + // not work on every platform. + Timer :: struct { + dummy: i32, + } +} + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // This allows the user to override the allocation functions. These should be + // set during application startup. + SetAllocator :: proc(allocFcn: AllocFcn, freefcn: FreeFcn) --- + // @return the total bytes allocated by Box2D + GetByteCount :: proc() -> c.int --- + // Override the default assert callback + // @param assertFcn a non-null assert callback + SetAssertFcn :: proc(assertfcn: AssertFcn) --- + + + CreateTimer :: proc() -> Timer --- + GetTicks :: proc(timer: ^Timer) -> i64 --- + GetMilliseconds :: proc(#by_ptr timer: Timer) -> f32 --- + GetMillisecondsAndReset :: proc(timer: ^Timer) -> f32 --- + SleepMilliseconds :: proc(milliseconds: c.int) --- + Yield :: proc() --- + + + // Box2D bases all length units on meters, but you may need different units for your game. + // You can set this value to use different units. This should be done at application startup + // and only modified once. Default value is 1. + // @warning This must be modified before any calls to Box2D + SetLengthUnitsPerMeter :: proc(lengthUnits: f32) --- + + // Get the current length units per meter. + GetLengthUnitsPerMeter :: proc() -> f32 --- +} + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // Use this to initialize your world definition + // @ingroup world + DefaultWorldDef :: proc() -> WorldDef --- + + // Use this to initialize your body definition + // @ingroup body + DefaultBodyDef :: proc() -> BodyDef --- + + // Use this to initialize your filter + // @ingroup shape + DefaultFilter :: proc() -> Filter --- + + // Use this to initialize your query filter + // @ingroup shape + DefaultQueryFilter :: proc() -> QueryFilter --- + + // Use this to initialize your shape definition + // @ingroup shape + DefaultShapeDef :: proc() -> ShapeDef --- + + // Use this to initialize your chain definition + // @ingroup shape + DefaultChainDef :: proc() -> ChainDef --- + + // Use this to initialize your joint definition + // @ingroup distance_joint + DefaultDistanceJointDef :: proc() -> DistanceJointDef --- + + // Use this to initialize your joint definition + // @ingroup motor_joint + DefaultMotorJointDef :: proc() -> MotorJointDef --- + + // Use this to initialize your joint definition + // @ingroup mouse_joint + DefaultMouseJointDef :: proc() -> MouseJointDef --- + + // Use this to initialize your joint definition + // @ingroupd prismatic_joint + DefaultPrismaticJointDef :: proc() -> PrismaticJointDef --- + + // Use this to initialize your joint definition. + // @ingroup revolute_joint + DefaultRevoluteJointDef :: proc() -> RevoluteJointDef --- + + // Use this to initialize your joint definition + // @ingroup weld_joint + DefaultWeldJointDef :: proc() -> WeldJointDef --- + + // Use this to initialize your joint definition + // @ingroup wheel_joint + DefaultWheelJointDef :: proc() -> WheelJointDef --- +} + + + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // Validate ray cast input data (NaN, etc) + IsValidRay :: proc(#by_ptr input: RayCastInput) -> bool --- + + // Make a convex polygon from a convex hull. This will assert if the hull is not valid. + // @warning Do not manually fill in the hull data, it must come directly from b2ComputeHull + MakePolygon :: proc(#by_ptr hull: Hull, radius: f32) -> Polygon --- + + // Make an offset convex polygon from a convex hull. This will assert if the hull is not valid. + // @warning Do not manually fill in the hull data, it must come directly from b2ComputeHull + MakeOffsetPolygon :: proc(#by_ptr hull: Hull, radius: f32, transform: Transform) -> Polygon --- + + // Make a square polygon, bypassing the need for a convex hull. + MakeSquare :: proc(h: f32) -> Polygon --- + + // Make a box (rectangle) polygon, bypassing the need for a convex hull. + MakeBox :: proc(hx, hy: f32) -> Polygon --- + + // Make a rounded box, bypassing the need for a convex hull. + MakeRoundedBox :: proc(hx, hy: f32, radius: f32) -> Polygon --- + + // Make an offset box, bypassing the need for a convex hull. + MakeOffsetBox :: proc(hx, hy: f32, center: Vec2, angle: f32) -> Polygon --- + + // Transform a polygon. This is useful for transferring a shape from one body to another. + TransformPolygon :: proc(transform: Transform, #by_ptr polygon: Polygon) -> Polygon --- + + // Compute mass properties of a circle + ComputeCircleMass :: proc(#by_ptr shape: Circle, density: f32) -> MassData --- + + // Compute mass properties of a capsule + ComputeCapsuleMass :: proc(#by_ptr shape: Capsule, density: f32) -> MassData --- + + // Compute mass properties of a polygon + ComputePolygonMass :: proc(#by_ptr shape: Polygon, density: f32) -> MassData --- + + // Compute the bounding box of a transformed circle + ComputeCircleAABB :: proc(#by_ptr shape: Circle, transform: Transform) -> AABB --- + + // Compute the bounding box of a transformed capsule + ComputeCapsuleAABB :: proc(#by_ptr shape: Capsule, transform: Transform) -> AABB --- + + // Compute the bounding box of a transformed polygon + ComputePolygonAABB :: proc(#by_ptr shape: Polygon, transform: Transform) -> AABB --- + + // Compute the bounding box of a transformed line segment + ComputeSegmentAABB :: proc(#by_ptr shape: Segment, transform: Transform) -> AABB --- + + // Test a point for overlap with a circle in local space + PointInCircle :: proc(point: Vec2, #by_ptr shape: Circle) -> bool --- + + // Test a point for overlap with a capsule in local space + PointInCapsule :: proc(point: Vec2, #by_ptr shape: Capsule) -> bool --- + + // Test a point for overlap with a convex polygon in local space + PointInPolygon :: proc(point: Vec2, #by_ptr shape: Polygon) -> bool --- + + // Ray cast versus circle in shape local space. Initial overlap is treated as a miss. + RayCastCircle :: proc(#by_ptr input: RayCastInput, #by_ptr shape: Circle) -> CastOutput --- + + // Ray cast versus capsule in shape local space. Initial overlap is treated as a miss. + RayCastCapsule :: proc(#by_ptr input: RayCastInput, #by_ptr shape: Capsule) -> CastOutput --- + + // Ray cast versus segment in shape local space. Optionally treat the segment as one-sided with hits from + // the left side being treated as a miss. + RayCastSegment :: proc(#by_ptr input: RayCastInput, #by_ptr shape: Segment, oneSided: bool) -> CastOutput --- + + // Ray cast versus polygon in shape local space. Initial overlap is treated as a miss. + RayCastPolygon :: proc(#by_ptr input: RayCastInput, #by_ptr shape: Polygon) -> CastOutput --- + + // Shape cast versus a circle. Initial overlap is treated as a miss. + ShapeCastCircle :: proc(#by_ptr input: ShapeCastInput, #by_ptr shape: Circle) -> CastOutput --- + + // Shape cast versus a capsule. Initial overlap is treated as a miss. + ShapeCastCapsule :: proc(#by_ptr input: ShapeCastInput, #by_ptr shape: Capsule) -> CastOutput --- + + // Shape cast versus a line segment. Initial overlap is treated as a miss. + ShapeCastSegment :: proc(#by_ptr input: ShapeCastInput, #by_ptr shape: Segment) -> CastOutput --- + + // Shape cast versus a convex polygon. Initial overlap is treated as a miss. + ShapeCastPolygon :: proc(#by_ptr input: ShapeCastInput, #by_ptr shape: Polygon) -> CastOutput --- +} + + +// Compute the convex hull of a set of points. Returns an empty hull if it fails. +// Some failure cases: +// - all points very close together +// - all points on a line +// - less than 3 points +// - more than maxPolygonVertices points +// This welds close points and removes collinear points. +// @warning Do not modify a hull once it has been computed +@(require_results) +ComputeHull :: proc "c" (points: []Vec2) -> Hull { + foreign lib { + b2ComputeHull :: proc "c" (points: [^]Vec2, count: i32) -> Hull --- + } + return b2ComputeHull(raw_data(points), i32(len(points))) +} + + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // This determines if a hull is valid. Checks for: + // - convexity + // - collinear points + // This is expensive and should not be called at runtime. + ValidateHull :: proc(#by_ptr hull: Hull) -> bool --- +} + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // Compute the distance between two line segments, clamping at the end points if needed. + SegmentDistance :: proc(p1, q1: Vec2, p2, q2: Vec2) -> SegmentDistanceResult --- +} + +// Compute the closest points between two shapes represented as point clouds. +// DistanceCache cache is input/output. On the first call set DistanceCache.count to zero. +// The underlying GJK algorithm may be debugged by passing in debug simplexes and capacity. You may pass in NULL and 0 for these. +@(require_results) +ShapeDistance :: proc "c" (cache: ^DistanceCache, #by_ptr input: DistanceInput, simplexes: []Simplex) -> DistanceOutput { + foreign lib { + b2ShapeDistance :: proc "c" (cache: ^DistanceCache, #by_ptr input: DistanceInput, simplexes: [^]Simplex, simplexCapacity: c.int) -> DistanceOutput --- + } + return b2ShapeDistance(cache, input, raw_data(simplexes), i32(len(simplexes))) +} + + +// Make a proxy for use in GJK and related functions. +@(require_results) +MakeProxy :: proc "c" (vertices: []Vec2, radius: f32) -> DistanceProxy { + foreign lib { + b2MakeProxy :: proc "c" (vertices: [^]Vec2, count: i32, radius: f32) -> DistanceProxy --- + } + return b2MakeProxy(raw_data(vertices), i32(len(vertices)), radius) +} + + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // Perform a linear shape cast of shape B moving and shape A fixed. Determines the hit point, normal, and translation fraction. + ShapeCast :: proc(#by_ptr input: ShapeCastPairInput) -> CastOutput --- + + // Evaluate the transform sweep at a specific time. + GetSweepTransform :: proc(#by_ptr sweep: Sweep, time: f32) -> Transform --- + + // Compute the upper bound on time before two shapes penetrate. Time is represented as + // a fraction between [0,tMax]. This uses a swept separating axis and may miss some intermediate, + // non-tunneling collisions. If you change the time interval, you should call this function + // again. + TimeOfImpact :: proc(#by_ptr input: TOIInput) -> TOIOutput --- +} + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // Compute the contact manifold between two circles + CollideCircles :: proc(#by_ptr circleA: Circle, xfA: Transform, #by_ptr circleB: Circle, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between a capsule and circle + CollideCapsuleAndCircle :: proc(#by_ptr capsuleA: Capsule, xfA: Transform, #by_ptr circleB: Circle, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between an segment and a circle + CollideSegmentAndCircle :: proc(#by_ptr segmentA: Segment, xfA: Transform, #by_ptr circleB: Circle, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between a polygon and a circle + CollidePolygonAndCircle :: proc(#by_ptr polygonA: Polygon, xfA: Transform, #by_ptr circleB: Circle, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between a capsule and circle + CollideCapsules :: proc(#by_ptr capsuleA: Capsule, xfA: Transform, #by_ptr capsuleB: Capsule, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between an segment and a capsule + CollideSegmentAndCapsule :: proc(#by_ptr segmentA: Segment, xfA: Transform, #by_ptr capsuleB: Capsule, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between a polygon and capsule + CollidePolygonAndCapsule :: proc(#by_ptr polygonA: Polygon, xfA: Transform, #by_ptr capsuleB: Capsule, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between two polygons + CollidePolygons :: proc(#by_ptr polygonA: Polygon, xfA: Transform, #by_ptr polygonB: Polygon, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between an segment and a polygon + CollideSegmentAndPolygon :: proc(#by_ptr segmentA: Segment, xfA: Transform, #by_ptr polygonB: Polygon, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between a smooth segment and a circle + CollideSmoothSegmentAndCircle :: proc(#by_ptr smoothSegmentA: SmoothSegment, xfA: Transform, #by_ptr circleB: Circle, xfB: Transform) -> Manifold --- + + // Compute the contact manifold between an segment and a capsule + CollideSmoothSegmentAndCapsule :: proc(#by_ptr smoothSegmentA: SmoothSegment, xfA: Transform, #by_ptr capsuleB: Capsule, xfB: Transform, cache: ^DistanceCache) -> Manifold --- + + // Compute the contact manifold between a smooth segment and a rounded polygon + CollideSmoothSegmentAndPolygon :: proc(#by_ptr smoothSegmentA: SmoothSegment, xfA: Transform, #by_ptr polygonB: Polygon, xfB: Transform, cache: ^DistanceCache) -> Manifold --- +} + + + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // Constructing the tree initializes the node pool. + DynamicTree_Create :: proc() -> DynamicTree --- + + // Destroy the tree, freeing the node pool. + DynamicTree_Destroy :: proc(tree: ^DynamicTree) --- + + // Create a proxy. Provide an AABB and a userData value. + DynamicTree_CreateProxy :: proc(tree: ^DynamicTree, aabb: AABB, categoryBits: u32, userData: i32) -> i32 --- + + // Destroy a proxy. This asserts if the id is invalid. + DynamicTree_DestroyProxy :: proc(tree: ^DynamicTree, proxyId: i32) --- + + // Move a proxy to a new AABB by removing and reinserting into the tree. + DynamicTree_MoveProxy :: proc(tree: ^DynamicTree, proxyId: i32, aabb: AABB) --- + + // Enlarge a proxy and enlarge ancestors as necessary. + DynamicTree_EnlargeProxy :: proc(tree: ^DynamicTree, proxyId: i32, aabb: AABB) --- + + // Query an AABB for overlapping proxies. The callback class + // is called for each proxy that overlaps the supplied AABB. + DynamicTree_Query :: proc(#by_ptr tree: DynamicTree, aabb: AABB, maskBits: u32, callback: TreeQueryCallbackFcn, ctx: rawptr) --- + + // Ray-cast against the proxies in the tree. This relies on the callback + // to perform a exact ray-cast in the case were the proxy contains a shape. + // The callback also performs the any collision filtering. This has performance + // roughly equal to k * log(n), where k is the number of collisions and n is the + // number of proxies in the tree. + // Bit-wise filtering using mask bits can greatly improve performance in some scenarios. + // @param tree the dynamic tree to ray cast + // @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1) + // @param maskBits filter bits: `bool accept = (maskBits & node->categoryBits) != 0 ---` + // @param callback a callback class that is called for each proxy that is hit by the ray + // @param context user context that is passed to the callback + DynamicTree_RayCast :: proc(#by_ptr tree: DynamicTree, #by_ptr input: RayCastInput, maskBits: u32, callback: TreeRayCastCallbackFcn, ctx: rawptr) --- + + // Ray-cast against the proxies in the tree. This relies on the callback + // to perform a exact ray-cast in the case were the proxy contains a shape. + // The callback also performs the any collision filtering. This has performance + // roughly equal to k * log(n), where k is the number of collisions and n is the + // number of proxies in the tree. + // @param tree the dynamic tree to ray cast + // @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). + // @param maskBits filter bits: `bool accept = (maskBits & node->categoryBits) != 0 ---` + // @param callback a callback class that is called for each proxy that is hit by the shape + // @param context user context that is passed to the callback + DynamicTree_ShapeCast :: proc(#by_ptr tree: DynamicTree, #by_ptr input: ShapeCastInput, maskBits: u32, callback: TreeShapeCastCallbackFcn, ctx: rawptr) --- + + // Validate this tree. For testing. + DynamicTree_Validate :: proc(#by_ptr tree: DynamicTree) --- + + // Compute the height of the binary tree in O(N) time. Should not be + // called often. + DynamicTree_GetHeight :: proc(#by_ptr tree: DynamicTree) -> c.int --- + + // Get the maximum balance of the tree. The balance is the difference in height of the two children of a node. + DynamicTree_GetMaxBalance :: proc(#by_ptr tree: DynamicTree) -> c.int --- + + // Get the ratio of the sum of the node areas to the root area. + DynamicTree_GetAreaRatio :: proc(#by_ptr tree: DynamicTree) -> f32 --- + + // Build an optimal tree. Very expensive. For testing. + DynamicTree_RebuildBottomUp :: proc(tree: ^DynamicTree) --- + + // Get the number of proxies created + DynamicTree_GetProxyCount :: proc(#by_ptr tree: DynamicTree) -> c.int --- + + // Rebuild the tree while retaining subtrees that haven't changed. Returns the number of boxes sorted. + DynamicTree_Rebuild :: proc(tree: ^DynamicTree, fullBuild: bool) -> c.int --- + + // Shift the world origin. Useful for large worlds. + // The shift formula is: position -= newOrigin + // @param tree the tree to shift + // @param newOrigin the new origin with respect to the old origin + DynamicTree_ShiftOrigin :: proc(tree: ^DynamicTree, newOrigin: Vec2) --- + + // Get the number of bytes used by this tree + DynamicTree_GetByteCount :: proc(#by_ptr tree: DynamicTree) -> c.int --- +} + +// Get proxy user data +// @return the proxy user data or 0 if the id is invalid +@(require_results) +DynamicTree_GetUserData :: #force_inline proc "contextless" (tree: DynamicTree, proxyId: i32) -> i32 { + return tree.nodes[proxyId].userData +} + +// Get the AABB of a proxy +@(require_results) +DynamicTree_GetAABB :: #force_inline proc "contextless" (tree: DynamicTree, proxyId: i32) -> AABB { + return tree.nodes[proxyId].aabb +} + + + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + /** + * @defgroup world World + * These functions allow you to create a simulation world. + * + * You can add rigid bodies and joint constraints to the world and run the simulation. You can get contact + * information to get contact points and normals as well as events. You can query to world, checking for overlaps and casting rays + * or shapes. There is also debugging information such as debug draw, timing information, and counters. You can find documentation + * here: https://box2d.org/ + */ + + // Create a world for rigid body simulation. A world contains bodies, shapes, and constraints. You make create + // up to 128 worlds. Each world is completely independent and may be simulated in parallel. + // @return the world id. + CreateWorld :: proc(#by_ptr def: WorldDef) -> WorldId --- + + // Destroy a world + DestroyWorld :: proc(worldId: WorldId) --- + + // World id validation. Provides validation for up to 64K allocations. + World_IsValid :: proc(id: WorldId) -> bool --- + + // Simulate a world for one time step. This performs collision detection, integration, and constraint solution. + // @param worldId The world to simulate + // @param timeStep The amount of time to simulate, this should be a fixed number. Typically 1/60. + // @param subStepCount The number of sub-steps, increasing the sub-step count can increase accuracy. Typically 4. + World_Step :: proc(worldId: WorldId, timeStep: f32 , subStepCount: c.int) --- + + // Call this to draw shapes and other debug draw data + World_Draw :: proc(worldId: WorldId, draw: DebugDraw) --- + + // Get the body events for the current time step. The event data is transient. Do not store a reference to this data. + World_GetBodyEvents :: proc(worldId: WorldId) -> BodyEvents --- + + // Get sensor events for the current time step. The event data is transient. Do not store a reference to this data. + World_GetSensorEvents :: proc(worldId: WorldId) -> SensorEvents --- + + // Get contact events for this current time step. The event data is transient. Do not store a reference to this data. + World_GetContactEvents :: proc(worldId: WorldId) -> ContactEvents --- + + // Overlap test for all shapes that *potentially* overlap the provided AABB + World_OverlapAABB :: proc(worldId: WorldId, aabb: AABB, filter: QueryFilter, fcn: OverlapResultFcn, ctx: rawptr) --- + + // Overlap test for for all shapes that overlap the provided circle + World_OverlapCircle :: proc(worldId: WorldId, #by_ptr circle: Circle, transform: Transform, filter: QueryFilter, fcn: OverlapResultFcn, ctx: rawptr) --- + + // Overlap test for all shapes that overlap the provided capsule + World_OverlapCapsule :: proc(worldId: WorldId, #by_ptr capsule: Capsule, transform: Transform, filter: QueryFilter, fcn: OverlapResultFcn, ctx: rawptr) --- + + // Overlap test for all shapes that overlap the provided polygon + World_OverlapPolygon :: proc(worldId: WorldId, #by_ptr polygon: Polygon, transform: Transform, filter: QueryFilter, fcn: OverlapResultFcn, ctx: rawptr) --- + + // Cast a ray into the world to collect shapes in the path of the ray. + // Your callback function controls whether you get the closest point, any point, or n-points. + // The ray-cast ignores shapes that contain the starting point. + // @param worldId The world to cast the ray against + // @param origin The start point of the ray + // @param translation The translation of the ray from the start point to the end point + // @param filter Contains bit flags to filter unwanted shapes from the results + // @param fcn A user implemented callback function + // @param context A user context that is passed along to the callback function + // @note The callback function may receive shapes in any order + World_CastRay :: proc(worldId: WorldId, origin: Vec2, translation: Vec2, filter: QueryFilter, fcn: CastResultFcn, ctx: rawptr) --- + + // Cast a ray into the world to collect the closest hit. This is a convenience function. + // This is less general than b2World_CastRay() and does not allow for custom filtering. + World_CastRayClosest :: proc(worldId: WorldId, origin: Vec2, translation: Vec2, filter: QueryFilter) -> RayResult --- + + // Cast a circle through the world. Similar to a cast ray except that a circle is cast instead of a point. + World_CastCircle :: proc(worldId: WorldId, #by_ptr circle: Circle, originTransform: Transform, translation: Vec2, filter: QueryFilter, fcn: CastResultFcn, ctx: rawptr) --- + + // Cast a capsule through the world. Similar to a cast ray except that a capsule is cast instead of a point. + World_CastCapsule :: proc(worldId: WorldId, #by_ptr capsule: Capsule, originTransform: Transform, translation: Vec2, filter: QueryFilter, fcn: CastResultFcn, ctx: rawptr) --- + + // Cast a polygon through the world. Similar to a cast ray except that a polygon is cast instead of a point. + World_CastPolygon :: proc(worldId: WorldId, #by_ptr polygon: Polygon, originTransform: Transform, translation: Vec2, filter: QueryFilter, fcn: CastResultFcn, ctx: rawptr) --- + + // Enable/disable sleep. If your application does not need sleeping, you can gain some performance + // by disabling sleep completely at the world level. + // @see WorldDef + World_EnableSleeping :: proc(worldId: WorldId, flag: bool) --- + + // Enable/disable continuous collision between dynamic and static bodies. Generally you should keep continuous + // collision enabled to prevent fast moving objects from going through static objects. The performance gain from + // disabling continuous collision is minor. + // @see WorldDef + World_EnableContinuous :: proc(worldId: WorldId, flag: bool) --- + + // Adjust the restitution threshold. It is recommended not to make this value very small + // because it will prevent bodies from sleeping. Typically in meters per second. + // @see WorldDef + World_SetRestitutionThreshold :: proc(worldId: WorldId, value: f32) --- + + // Adjust the hit event threshold. This controls the collision velocity needed to generate a b2ContactHitEvent. + // Typically in meters per second. + // @see WorldDef::hitEventThreshold + World_SetHitEventThreshold :: proc(worldId: WorldId, value: f32) --- + + // Register the custom filter callback. This is optional. + World_SetCustomFilterCallback :: proc(worldId: WorldId, fcn: CustomFilterFcn, ctx: rawptr) --- + + // Register the pre-solve callback. This is optional. + World_SetPreSolveCallback :: proc(worldId: WorldId, fcn: PreSolveFcn, ctx: rawptr) --- + + // Set the gravity vector for the entire world. Box2D has no concept of an up direction and this + // is left as a decision for the application. Typically in m/s^2. + // @see WorldDef + World_SetGravity :: proc(worldId: WorldId, gravity: Vec2) --- + + // Get the gravity vector + World_GetGravity :: proc(worldId: WorldId) -> Vec2 --- + + // Apply a radial explosion + // @param worldId The world id + // @param position The center of the explosion + // @param radius The radius of the explosion + // @param impulse The impulse of the explosion, typically in kg * m / s or N * s. + World_Explode :: proc(worldId: WorldId, position: Vec2, radius: f32, impulse: f32) --- + + // Adjust contact tuning parameters + // @param worldId The world id + // @param hertz The contact stiffness (cycles per second) + // @param dampingRatio The contact bounciness with 1 being critical damping (non-dimensional) + // @param pushVelocity The maximum contact constraint push out velocity (meters per second) + // @note Advanced feature + World_SetContactTuning :: proc(worldId: WorldId, hertz: f32, dampingRatio: f32, pushVelocity: f32) --- + + // Enable/disable constraint warm starting. Advanced feature for testing. Disabling + // sleeping greatly reduces stability and provides no performance gain. + World_EnableWarmStarting :: proc(worldId: WorldId, flag: bool) --- + + // Get the current world performance profile + World_GetProfile :: proc(worldId: WorldId) -> Profile --- + + // Get world counters and sizes + World_GetCounters :: proc(worldId: WorldId) -> Counters --- + + // Dump memory stats to box2d_memory.txt + World_DumpMemoryStats :: proc(worldId: WorldId) --- +} + + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + /** + * @defgroup body Body + * This is the body API. + */ + + // Create a rigid body given a definition. No reference to the definition is retained. So you can create the definition + // on the stack and pass it as a pointer. + // @code{.odin} + // body_def := b2.DefaultBodyDef() + // my_body_id =: b2.CreateBody(my_world_id, body_def) + // @endcode + // @warning This function is locked during callbacks. + CreateBody :: proc(worldId: WorldId, #by_ptr def: BodyDef) -> BodyId --- + + // Destroy a rigid body given an id. This destroys all shapes and joints attached to the body. + // Do not keep references to the associated shapes and joints. + DestroyBody :: proc(bodyId: BodyId) --- + + // Body identifier validation. Can be used to detect orphaned ids. Provides validation for up to 64K allocations. + Body_IsValid :: proc(id: BodyId) -> bool --- + + // Get the body type: static, kinematic, or dynamic + Body_GetType :: proc(bodyId: BodyId) -> BodyType --- + + // Change the body type. This is an expensive operation. This automatically updates the mass + // properties regardless of the automatic mass setting. + Body_SetType :: proc(bodyId: BodyId, type: BodyType) --- + + // Set the user data for a body + Body_SetUserData :: proc(bodyId: BodyId, userData: rawptr) --- + + // Get the user data stored in a body + Body_GetUserData :: proc(bodyId: BodyId) -> rawptr --- + + // Get the world position of a body. This is the location of the body origin. + Body_GetPosition :: proc(bodyId: BodyId) -> Vec2 --- + + // Get the world rotation of a body as a cosine/sine pair (complex number) + Body_GetRotation :: proc(bodyId: BodyId) -> Rot --- + + // Get the world transform of a body. + Body_GetTransform :: proc(bodyId: BodyId) -> Transform --- + + // Set the world transform of a body. This acts as a teleport and is fairly expensive. + // @note Generally you should create a body with then intended transform. + // @see BodyDef::position and BodyDef::angle + Body_SetTransform :: proc(bodyId: BodyId, position: Vec2, rotation: Rot) --- + + // Get a local point on a body given a world point + Body_GetLocalPoint :: proc(bodyId: BodyId, worldPoint: Vec2) -> Vec2 --- + + // Get a world point on a body given a local point + Body_GetWorldPoint :: proc(bodyId: BodyId, localPoint: Vec2) -> Vec2 --- + + // Get a local vector on a body given a world vector + Body_GetLocalVector :: proc(bodyId: BodyId, worldVector: Vec2) -> Vec2 --- + + // Get a world vector on a body given a local vector + Body_GetWorldVector :: proc(bodyId: BodyId, localVector: Vec2) -> Vec2 --- + + // Get the linear velocity of a body's center of mass. Typically in meters per second. + Body_GetLinearVelocity :: proc(bodyId: BodyId) -> Vec2 --- + + // Get the angular velocity of a body in radians per second + Body_GetAngularVelocity :: proc(bodyId: BodyId) -> f32 --- + + // Set the linear velocity of a body. Typically in meters per second. + Body_SetLinearVelocity :: proc(bodyId: BodyId, linearVelocity: Vec2) --- + + // Set the angular velocity of a body in radians per second + Body_SetAngularVelocity :: proc(bodyId: BodyId, angularVelocity: f32) --- + + // Apply a force at a world point. If the force is not applied at the center of mass, + // it will generate a torque and affect the angular velocity. This optionally wakes up the body. + // The force is ignored if the body is not awake. + // @param bodyId The body id + // @param force The world force vector, typically in newtons (N) + // @param point The world position of the point of application + // @param wake Option to wake up the body + Body_ApplyForce :: proc(bodyId: BodyId, force: Vec2, point: Vec2, wake: bool) --- + + // Apply a force to the center of mass. This optionally wakes up the body. + // The force is ignored if the body is not awake. + // @param bodyId The body id + // @param force the world force vector, usually in newtons (N). + // @param wake also wake up the body + Body_ApplyForceToCenter :: proc(bodyId: BodyId, force: Vec2, wake: bool) --- + + // Apply a torque. This affects the angular velocity without affecting the linear velocity. + // This optionally wakes the body. The torque is ignored if the body is not awake. + // @param bodyId The body id + // @param torque about the z-axis (out of the screen), typically in N*m. + // @param wake also wake up the body + Body_ApplyTorque :: proc(bodyId: BodyId, torque: f32, wake: bool) --- + + // Apply an impulse at a point. This immediately modifies the velocity. + // It also modifies the angular velocity if the point of application + // is not at the center of mass. This optionally wakes the body. + // The impulse is ignored if the body is not awake. + // @param bodyId The body id + // @param impulse the world impulse vector, typically in N*s or kg*m/s. + // @param point the world position of the point of application. + // @param wake also wake up the body + // @warning This should be used for one-shot impulses. If you need a steady force, + // use a force instead, which will work better with the sub-stepping solver. + Body_ApplyLinearImpulse :: proc(bodyId: BodyId, impulse: Vec2, point: Vec2, wake: bool) --- + + // Apply an impulse to the center of mass. This immediately modifies the velocity. + // The impulse is ignored if the body is not awake. This optionally wakes the body. + // @param bodyId The body id + // @param impulse the world impulse vector, typically in N*s or kg*m/s. + // @param wake also wake up the body + // @warning This should be used for one-shot impulses. If you need a steady force, + // use a force instead, which will work better with the sub-stepping solver. + Body_ApplyLinearImpulseToCenter :: proc(bodyId: BodyId, impulse: Vec2, wake: bool) --- + + // Apply an angular impulse. The impulse is ignored if the body is not awake. + // This optionally wakes the body. + // @param bodyId The body id + // @param impulse the angular impulse, typically in units of kg*m*m/s + // @param wake also wake up the body + // @warning This should be used for one-shot impulses. If you need a steady force, + // use a force instead, which will work better with the sub-stepping solver. + Body_ApplyAngularImpulse :: proc(bodyId: BodyId, impulse: f32, wake: bool) --- + + // Get the mass of the body, typically in kilograms + Body_GetMass :: proc(bodyId: BodyId) -> f32 --- + + // Get the inertia tensor of the body, typically in kg*m^2 + Body_GetInertiaTensor :: proc(bodyId: BodyId) -> f32 --- + + // Get the center of mass position of the body in local space + Body_GetLocalCenterOfMass :: proc(bodyId: BodyId) -> Vec2 --- + + // Get the center of mass position of the body in world space + Body_GetWorldCenterOfMass :: proc(bodyId: BodyId) -> Vec2 --- + + // Override the body's mass properties. Normally this is computed automatically using the + // shape geometry and density. This information is lost if a shape is added or removed or if the + // body type changes. + Body_SetMassData :: proc(bodyId: BodyId, massData: MassData) --- + + // Get the mass data for a body + Body_GetMassData :: proc(bodyId: BodyId) -> MassData --- + + // This update the mass properties to the sum of the mass properties of the shapes. + // This normally does not need to be called unless you called SetMassData to override + // the mass and you later want to reset the mass. + // You may also use this when automatic mass computation has been disabled. + // You should call this regardless of body type. + Body_ApplyMassFromShapes :: proc(bodyId: BodyId) --- + + // Set the automatic mass setting. Normally this is set in BodyDef before creation. + // @see BodyDef::automaticMass + Body_SetAutomaticMass :: proc(bodyId: BodyId, automaticMass: bool ) --- + + // Get the automatic mass setting + Body_GetAutomaticMass :: proc(bodyId: BodyId) -> bool --- + + // Adjust the linear damping. Normally this is set in BodyDef before creation. + Body_SetLinearDamping :: proc(bodyId: BodyId, linearDamping: f32) --- + + // Get the current linear damping. + Body_GetLinearDamping :: proc(bodyId: BodyId) -> f32 --- + + // Adjust the angular damping. Normally this is set in BodyDef before creation. + Body_SetAngularDamping :: proc(bodyId: BodyId, angularDamping: f32) --- + + // Get the current angular damping. + Body_GetAngularDamping :: proc(bodyId: BodyId) -> f32 --- + + // Adjust the gravity scale. Normally this is set in BodyDef before creation. + // @see BodyDef::gravityScale + Body_SetGravityScale :: proc(bodyId: BodyId, gravityScale: f32) --- + + // Get the current gravity scale + Body_GetGravityScale :: proc(bodyId: BodyId) -> f32 --- + + // @return true if this body is awake + Body_IsAwake :: proc(bodyId: BodyId) -> bool --- + + // Wake a body from sleep. This wakes the entire island the body is touching. + // @warning Putting a body to sleep will put the entire island of bodies touching this body to sleep, + // which can be expensive and possibly unintuitive. + Body_SetAwake :: proc(bodyId: BodyId, awake: bool) --- + + // Enable or disable sleeping for this body. If sleeping is disabled the body will wake. + Body_EnableSleep :: proc(bodyId: BodyId, enableSleep: bool) --- + + // Returns true if sleeping is enabled for this body + Body_IsSleepEnabled :: proc(bodyId: BodyId) -> bool --- + + // Set the sleep threshold, typically in meters per second + Body_SetSleepThreshold :: proc(bodyId: BodyId, sleepVelocity: f32) --- + + // Get the sleep threshold, typically in meters per second. + Body_GetSleepThreshold :: proc(bodyId: BodyId) -> f32 --- + + // Returns true if this body is enabled + Body_IsEnabled :: proc(bodyId: BodyId) -> bool --- + + // Disable a body by removing it completely from the simulation. This is expensive. + Body_Disable :: proc(bodyId: BodyId) --- + + // Enable a body by adding it to the simulation. This is expensive. + Body_Enable :: proc(bodyId: BodyId) --- + + // Set this body to have fixed rotation. This causes the mass to be reset in all cases. + Body_SetFixedRotation :: proc(bodyId: BodyId, flag: bool) --- + + // Does this body have fixed rotation? + Body_IsFixedRotation :: proc(bodyId: BodyId) -> bool --- + + // Set this body to be a bullet. A bullet does continuous collision detection + // against dynamic bodies (but not other bullets). + Body_SetBullet :: proc(bodyId: BodyId, flag: bool) --- + + // Is this body a bullet? + Body_IsBullet :: proc(bodyId: BodyId) -> bool --- + + // Enable/disable hit events on all shapes + // @see b2ShapeDef::enableHitEvents + Body_EnableHitEvents :: proc(bodyId: BodyId, enableHitEvents: bool) --- + + // Get the number of shapes on this body + Body_GetShapeCount :: proc(bodyId: BodyId) -> c.int --- + + // Get the number of joints on this body + Body_GetJointCount :: proc(bodyId: BodyId) -> c.int --- + + // Get the maximum capacity required for retrieving all the touching contacts on a body + Body_GetContactCapacity :: proc(bodyId: BodyId) -> c.int --- + + // Get the current world AABB that contains all the attached shapes. Note that this may not encompass the body origin. + // If there are no shapes attached then the returned AABB is empty and centered on the body origin. + Body_ComputeAABB :: proc(bodyId: BodyId) -> AABB --- +} + +// Get the shape ids for all shapes on this body, up to the provided capacity. +// @returns the shape ids stored in the user array +@(require_results) +Body_GetShapes :: proc "c" (bodyId: BodyId, shapeArray: []ShapeId) -> []ShapeId { + foreign lib { + b2Body_GetShapes :: proc "c" (bodyId: BodyId, shapeArray: [^]ShapeId, capacity: c.int) -> c.int --- + } + n := b2Body_GetShapes(bodyId, raw_data(shapeArray), c.int(len(shapeArray))) + return shapeArray[:n] + +} + +// Get the joint ids for all joints on this body, up to the provided capacity +// @returns the joint ids stored in the user array +@(require_results) +Body_GetJoints :: proc "c" (bodyId: BodyId, jointArray: []JointId) -> []JointId { + foreign lib { + b2Body_GetJoints :: proc "c" (bodyId: BodyId, jointArray: [^]JointId, capacity: c.int) -> c.int --- + } + n := b2Body_GetJoints(bodyId, raw_data(jointArray), c.int(len(jointArray))) + return jointArray[:n] + +} + +// Get the touching contact data for a body +@(require_results) +Body_GetContactData :: proc "c" (bodyId: BodyId, contactData: []ContactData) -> []ContactData { + foreign lib { + b2Body_GetContactData :: proc "c" (bodyId: BodyId, contactData: [^]ContactData, capacity: c.int) -> c.int --- + } + n := b2Body_GetContactData(bodyId, raw_data(contactData), c.int(len(contactData))) + return contactData[:n] + +} + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + /** + * @defgroup shape Shape + * Functions to create, destroy, and access. + * Shapes bind raw geometry to bodies and hold material properties including friction and restitution. + */ + + // Create a circle shape and attach it to a body. The shape definition and geometry are fully cloned. + // Contacts are not created until the next time step. + // @return the shape id for accessing the shape + CreateCircleShape :: proc(bodyId: BodyId, #by_ptr def: ShapeDef, #by_ptr circle: Circle) -> ShapeId --- + + // Create a line segment shape and attach it to a body. The shape definition and geometry are fully cloned. + // Contacts are not created until the next time step. + // @return the shape id for accessing the shape + CreateSegmentShape :: proc(bodyId: BodyId, #by_ptr def: ShapeDef, #by_ptr segment: Segment) -> ShapeId --- + + // Create a capsule shape and attach it to a body. The shape definition and geometry are fully cloned. + // Contacts are not created until the next time step. + // @return the shape id for accessing the shape + CreateCapsuleShape :: proc(bodyId: BodyId, #by_ptr def: ShapeDef, #by_ptr capsule: Capsule) -> ShapeId --- + + // Create a polygon shape and attach it to a body. The shape definition and geometry are fully cloned. + // Contacts are not created until the next time step. + // @return the shape id for accessing the shape + CreatePolygonShape :: proc(bodyId: BodyId, #by_ptr def: ShapeDef, #by_ptr polygon: Polygon) -> ShapeId --- + + // Destroy a shape + DestroyShape :: proc(shapeId: ShapeId) --- + + // Shape identifier validation. Provides validation for up to 64K allocations. + Shape_IsValid :: proc(id: ShapeId) -> bool --- + + // Get the type of a shape + Shape_GetType :: proc(shapeId: ShapeId) -> ShapeType --- + + // Get the id of the body that a shape is attached to + Shape_GetBody :: proc(shapeId: ShapeId) -> BodyId --- + + // Returns true If the shape is a sensor + Shape_IsSensor :: proc(shapeId: ShapeId) -> bool --- + + // Set the user data for a shape + Shape_SetUserData :: proc(shapeId: ShapeId, userData: rawptr) --- + + // Get the user data for a shape. This is useful when you get a shape id + // from an event or query. + Shape_GetUserData :: proc(shapeId: ShapeId) -> rawptr --- + + // Set the mass density of a shape, typically in kg/m^2. + // This will not update the mass properties on the parent body. + // @see b2ShapeDef::density, b2Body_ApplyMassFromShapes + Shape_SetDensity :: proc(shapeId: ShapeId, density: f32) --- + + // Get the density of a shape, typically in kg/m^2 + Shape_GetDensity :: proc(shapeId: ShapeId) -> f32 --- + + // Set the friction on a shape + // @see b2ShapeDef::friction + Shape_SetFriction :: proc(shapeId: ShapeId, friction: f32) --- + + // Get the friction of a shape + Shape_GetFriction :: proc(shapeId: ShapeId) -> f32 --- + + // Set the shape restitution (bounciness) + // @see b2ShapeDef::restitution + Shape_SetRestitution :: proc(shapeId: ShapeId, restitution: f32) --- + + // Get the shape restitution + Shape_GetRestitution :: proc(shapeId: ShapeId) -> f32 --- + + // Get the shape filter + Shape_GetFilter :: proc(shapeId: ShapeId) -> Filter --- + + // Set the current filter. This is almost as expensive as recreating the shape. + // @see b2ShapeDef::filter + Shape_SetFilter :: proc(shapeId: ShapeId, filter: Filter) --- + + // Enable sensor events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. + // @see b2ShapeDef::isSensor + Shape_EnableSensorEvents :: proc(shapeId: ShapeId, flag: bool) --- + + // Returns true if sensor events are enabled + Shape_AreSensorEventsEnabled :: proc(shapeId: ShapeId) -> bool --- + + // Enable contact events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. + // @see b2ShapeDef::enableContactEvents + Shape_EnableContactEvents :: proc(shapeId: ShapeId, flag: bool) --- + + // Returns true if contact events are enabled + Shape_AreContactEventsEnabled :: proc(shapeId: ShapeId) -> bool --- + + // Enable pre-solve contact events for this shape. Only applies to dynamic bodies. These are expensive + // and must be carefully handled due to multithreading. Ignored for sensors. + // @see b2PreSolveFcn + Shape_EnablePreSolveEvents :: proc(shapeId: ShapeId, flag: bool) --- + + // Returns true if pre-solve events are enabled + Shape_ArePreSolveEventsEnabled :: proc(shapeId: ShapeId) -> bool --- + + // Enable contact hit events for this shape. Ignored for sensors. + // @see WorldDef.hitEventThreshold + Shape_EnableHitEvents :: proc(shapeId: ShapeId, flag: bool) --- + + // Returns true if hit events are enabled + Shape_AreHitEventsEnabled :: proc(shapeId: ShapeId) -> bool --- + + // Test a point for overlap with a shape + Shape_TestPoint :: proc(shapeId: ShapeId, point: Vec2) -> bool --- + + // Ray cast a shape directly + Shape_RayCast :: proc(shapeId: ShapeId, origin: Vec2, translation: Vec2) -> CastOutput --- + + // Get a copy of the shape's circle. Asserts the type is correct. + Shape_GetCircle :: proc(shapeId: ShapeId) -> Circle --- + + // Get a copy of the shape's line segment. Asserts the type is correct. + Shape_GetSegment :: proc(shapeId: ShapeId) -> Segment --- + + // Get a copy of the shape's smooth line segment. These come from chain shapes. + // Asserts the type is correct. + Shape_GetSmoothSegment :: proc(shapeId: ShapeId) -> SmoothSegment --- + + // Get a copy of the shape's capsule. Asserts the type is correct. + Shape_GetCapsule :: proc(shapeId: ShapeId) -> Capsule --- + + // Get a copy of the shape's convex polygon. Asserts the type is correct. + Shape_GetPolygon :: proc(shapeId: ShapeId) -> Polygon --- + + // Allows you to change a shape to be a circle or update the current circle. + // This does not modify the mass properties. + // @see b2Body_ApplyMassFromShapes + Shape_SetCircle :: proc(shapeId: ShapeId, #by_ptr circle: Circle) --- + + // Allows you to change a shape to be a capsule or update the current capsule. + // This does not modify the mass properties. + // @see b2Body_ApplyMassFromShapes + Shape_SetCapsule :: proc(shapeId: ShapeId, #by_ptr capsule: Capsule) --- + + // Allows you to change a shape to be a segment or update the current segment. + Shape_SetSegment :: proc(shapeId: ShapeId, #by_ptr segment: Segment) --- + + // Allows you to change a shape to be a polygon or update the current polygon. + // This does not modify the mass properties. + // @see b2Body_ApplyMassFromShapes + Shape_SetPolygon :: proc(shapeId: ShapeId, #by_ptr polygon: Polygon) --- + + // Get the parent chain id if the shape type is b2_smoothSegmentShape, otherwise + // returns b2_nullChainId. + Shape_GetParentChain :: proc(shapeId: ShapeId) -> ChainId --- + + // Get the maximum capacity required for retrieving all the touching contacts on a shape + Shape_GetContactCapacity :: proc(shapeId: ShapeId) -> c.int --- + + // Get the current world AABB + Shape_GetAABB :: proc(shapeId: ShapeId) -> AABB --- + + // Get the closest point on a shape to a target point. Target and result are in world space. + Shape_GetClosestPoint :: proc(shapeId: ShapeId, target: Vec2) -> Vec2 --- +} + +// Get the touching contact data for a shape. The provided shapeId will be either shapeIdA or shapeIdB on the contact data. +@(require_results) +Shape_GetContactData :: proc "c" (shapeId: ShapeId, contactData: []ContactData) -> []ContactData { + foreign lib { + b2Shape_GetContactData :: proc "c" (shapeId: ShapeId, contactData: [^]ContactData, capacity: c.int) -> c.int --- + } + n := b2Shape_GetContactData(shapeId, raw_data(contactData), c.int(len(contactData))) + return contactData[:n] +} + + +@(link_prefix="b2", default_calling_convention="c", require_results) +foreign lib { + // Chain Shape + + // Create a chain shape + // @see b2ChainDef for details + CreateChain :: proc(bodyId: BodyId, #by_ptr def: ChainDef) -> ChainId --- + + // Destroy a chain shape + DestroyChain :: proc(chainId: ChainId) --- + + // Set the chain friction + // @see b2ChainDef::friction + Chain_SetFriction :: proc(chainId: ChainId, friction: f32) --- + + // Set the chain restitution (bounciness) + // @see b2ChainDef::restitution + Chain_SetRestitution :: proc(chainId: ChainId, restitution: f32) --- + + // Chain identifier validation. Provides validation for up to 64K allocations. + Chain_IsValid :: proc(id: ChainId) -> bool --- + + /** + * @defgroup joint Joint + * @brief Joints allow you to connect rigid bodies together while allowing various forms of relative motions. + */ + + // Destroy a joint + DestroyJoint :: proc(jointId: JointId) --- + + // Joint identifier validation. Provides validation for up to 64K allocations. + Joint_IsValid :: proc(id: JointId) -> bool --- + + // Get the joint type + Joint_GetType :: proc(jointId: JointId) -> JointType --- + + // Get body A id on a joint + Joint_GetBodyA :: proc(jointId: JointId) -> BodyId --- + + // Get body B id on a joint + Joint_GetBodyB :: proc(jointId: JointId) -> BodyId --- + + // Get the local anchor on bodyA + Joint_GetLocalAnchorA :: proc(jointId: JointId) -> Vec2 --- + + // Get the local anchor on bodyB + Joint_GetLocalAnchorB :: proc(jointId: JointId) -> Vec2 --- + + // Toggle collision between connected bodies + Joint_SetCollideConnected :: proc(jointId: JointId, shouldCollide: bool) --- + + // Is collision allowed between connected bodies? + Joint_GetCollideConnected :: proc(jointId: JointId) -> bool --- + + // Set the user data on a joint + Joint_SetUserData :: proc(jointId: JointId, userData: rawptr) --- + + // Get the user data on a joint + Joint_GetUserData :: proc(jointId: JointId) -> rawptr --- + + // Wake the bodies connect to this joint + Joint_WakeBodies :: proc(jointId: JointId) --- + + // Get the current constraint force for this joint + Joint_GetConstraintForce :: proc(jointId: JointId) -> Vec2 --- + + // Get the current constraint torque for this joint + Joint_GetConstraintTorque :: proc(jointId: JointId) -> f32 --- + + /** + * @defgroup distance_joint Distance Joint + * @brief Functions for the distance joint. + */ + + // Create a distance joint + // @see b2DistanceJointDef for details + CreateDistanceJoint :: proc(worldId: WorldId, #by_ptr def: DistanceJointDef) -> JointId --- + + // Set the rest length of a distance joint + // @param jointId The id for a distance joint + // @param length The new distance joint length + DistanceJoint_SetLength :: proc(jointId: JointId, length: f32) --- + + // Get the rest length of a distance joint + DistanceJoint_GetLength :: proc(jointId: JointId) -> f32 --- + + // Enable/disable the distance joint spring. When disabled the distance joint is rigid. + DistanceJoint_EnableSpring :: proc(jointId: JointId, enableSpring: bool) --- + + // Is the distance joint spring enabled? + DistanceJoint_IsSpringEnabled :: proc(jointId: JointId) -> bool --- + + // Set the spring stiffness in Hertz + DistanceJoint_SetSpringHertz :: proc(jointId: JointId, hertz: f32) --- + + // Set the spring damping ratio, non-dimensional + DistanceJoint_SetSpringDampingRatio :: proc(jointId: JointId, dampingRatio: f32) --- + + // Get the spring Hertz + DistanceJoint_GetHertz :: proc(jointId: JointId) -> f32 --- + + // Get the spring damping ratio + DistanceJoint_GetDampingRatio :: proc(jointId: JointId) -> f32 --- + + // Enable joint limit. The limit only works if the joint spring is enabled. Otherwise the joint is rigid + // and the limit has no effect. + DistanceJoint_EnableLimit :: proc(jointId: JointId, enableLimit: bool) --- + + // Is the distance joint limit enabled? + DistanceJoint_IsLimitEnabled :: proc(jointId: JointId) -> bool --- + + // Set the minimum and maximum length parameters of a distance joint + DistanceJoint_SetLengthRange :: proc(jointId: JointId, minLength, maxLength: f32) --- + + // Get the distance joint minimum length + DistanceJoint_GetMinLength :: proc(jointId: JointId) -> f32 --- + + // Get the distance joint maximum length + DistanceJoint_GetMaxLength :: proc(jointId: JointId) -> f32 --- + + // Get the current length of a distance joint + DistanceJoint_GetCurrentLength :: proc(jointId: JointId) -> f32 --- + + // Enable/disable the distance joint motor + DistanceJoint_EnableMotor :: proc(jointId: JointId, enableMotor: bool) --- + + // Is the distance joint motor enabled? + DistanceJoint_IsMotorEnabled :: proc(jointId: JointId) -> bool --- + + // Set the distance joint motor speed, typically in meters per second + DistanceJoint_SetMotorSpeed :: proc(jointId: JointId, motorSpeed: f32) --- + + // Get the distance joint motor speed, typically in meters per second + DistanceJoint_GetMotorSpeed :: proc(jointId: JointId) -> f32 --- + + // Set the distance joint maximum motor force, typically in newtons + DistanceJoint_SetMaxMotorForce :: proc(jointId: JointId, force: f32) --- + + // Get the distance joint maximum motor force, typically in newtons + DistanceJoint_GetMaxMotorForce :: proc(jointId: JointId) -> f32 --- + + // Get the distance joint current motor force, typically in newtons + DistanceJoint_GetMotorForce :: proc(jointId: JointId) -> f32 --- + + /** + * @defgroup motor_joint Motor Joint + * @brief Functions for the motor joint. + * + * The motor joint is used to drive the relative transform between two bodies. It takes + * a relative position and rotation and applies the forces and torques needed to achieve + * that relative transform over time. + */ + + // Create a motor joint + // @see b2MotorJointDef for details + CreateMotorJoint :: proc(worldId: WorldId, def: MotorJointDef) -> JointId --- + + // Set the motor joint linear offset target + MotorJoint_SetLinearOffset :: proc(jointId: JointId, linearOffset: Vec2) --- + + // Get the motor joint linear offset target + MotorJoint_GetLinearOffset :: proc(jointId: JointId) -> Vec2 --- + + // Set the motor joint angular offset target in radians + MotorJoint_SetAngularOffset :: proc(jointId: JointId, angularOffset: f32) --- + + // Get the motor joint angular offset target in radians + MotorJoint_GetAngularOffset :: proc(jointId: JointId) -> f32 --- + + // Set the motor joint maximum force, typically in newtons + MotorJoint_SetMaxForce :: proc(jointId: JointId, maxForce: f32) --- + + // Get the motor joint maximum force, typically in newtons + MotorJoint_GetMaxForce :: proc(jointId: JointId) -> f32 --- + + // Set the motor joint maximum torque, typically in newton-meters + MotorJoint_SetMaxTorque :: proc(jointId: JointId, maxTorque: f32) --- + + // Get the motor joint maximum torque, typically in newton-meters + MotorJoint_GetMaxTorque :: proc(jointId: JointId) -> f32 --- + + // Set the motor joint correction factor, typically in [0, 1] + MotorJoint_SetCorrectionFactor :: proc(jointId: JointId, correctionFactor: f32) --- + + // Get the motor joint correction factor, typically in [0, 1] + MotorJoint_GetCorrectionFactor :: proc(jointId: JointId) -> f32 --- + + /**@}*/ + + /** + * @defgroup mouse_joint Mouse Joint + * @brief Functions for the mouse joint. + * + * The mouse joint is designed for use in the samples application, but you may find it useful in applications where + * the user moves a rigid body with a cursor. + */ + + // Create a mouse joint + // @see b2MouseJointDef for details + CreateMouseJoint :: proc(worldId: WorldId, #by_ptr def: MouseJointDef) -> JointId --- + + // Set the mouse joint target + MouseJoint_SetTarget :: proc(jointId: JointId, target: Vec2) --- + + // Get the mouse joint target + MouseJoint_GetTarget :: proc(jointId: JointId) -> Vec2 --- + + // Set the mouse joint spring stiffness in Hertz + MouseJoint_SetSpringHertz :: proc(jointId: JointId, hertz: f32) --- + + // Get the mouse joint spring stiffness in Hertz + MouseJoint_GetSpringHertz :: proc(jointId: JointId) -> f32 --- + + // Set the mouse joint spring damping ratio, non-dimensional + MouseJoint_SetSpringDampingRatio :: proc(jointId: JointId, dampingRatio: f32) --- + + // Get the mouse joint damping ratio, non-dimensional + MouseJoint_GetSpringDampingRatio :: proc(jointId: JointId) -> f32 --- + + // Set the mouse joint maximum force, typically in newtons + MouseJoint_SetMaxForce :: proc(jointId: JointId, maxForce: f32) --- + + // Get the mouse joint maximum force, typically in newtons + MouseJoint_GetMaxForce :: proc(jointId: JointId) -> f32 --- + + /**@}*/ + + /** + * @defgroup prismatic_joint Prismatic Joint + * @brief A prismatic joint allows for translation along a single axis with no rotation. + * + * The prismatic joint is useful for things like pistons and moving platforms, where you want a body to translate + * along an axis and have no rotation. Also called a *slider* joint. + */ + + // Create a prismatic (slider) joint. + // @see b2PrismaticJointDef for details + CreatePrismaticJoint :: proc(worldId: WorldId, #by_ptr def: PrismaticJointDef) -> JointId --- + + // Enable/disable the joint spring. + PrismaticJoint_EnableSpring :: proc(jointId: JointId, enableSpring: bool) --- + + // Is the prismatic joint spring enabled or not? + PrismaticJoint_IsSpringEnabled :: proc(jointId: JointId) -> bool --- + + // Set the prismatic joint stiffness in Hertz. + // This should usually be less than a quarter of the simulation rate. For example, if the simulation + // runs at 60Hz then the joint stiffness should be 15Hz or less. + PrismaticJoint_SetSpringHertz :: proc(jointId: JointId, hertz: f32) --- + + // Get the prismatic joint stiffness in Hertz + PrismaticJoint_GetSpringHertz :: proc(jointId: JointId) -> f32 --- + + // Set the prismatic joint damping ratio (non-dimensional) + PrismaticJoint_SetSpringDampingRatio :: proc(jointId: JointId, dampingRatio: f32) --- + + // Get the prismatic spring damping ratio (non-dimensional) + PrismaticJoint_GetSpringDampingRatio :: proc(jointId: JointId) -> f32 --- + + // Enable/disable a prismatic joint limit + PrismaticJoint_EnableLimit :: proc(jointId: JointId, enableLimit: bool) --- + + // Is the prismatic joint limit enabled? + PrismaticJoint_IsLimitEnabled :: proc(jointId: JointId) -> bool --- + + // Get the prismatic joint lower limit + PrismaticJoint_GetLowerLimit :: proc(jointId: JointId) -> f32 --- + + // Get the prismatic joint upper limit + PrismaticJoint_GetUpperLimit :: proc(jointId: JointId) -> f32 --- + + // Set the prismatic joint limits + PrismaticJoint_SetLimits :: proc(jointId: JointId, lower, upper: f32) --- + + // Enable/disable a prismatic joint motor + PrismaticJoint_EnableMotor :: proc(jointId: JointId, enableMotor: bool) --- + + // Is the prismatic joint motor enabled? + PrismaticJoint_IsMotorEnabled :: proc(jointId: JointId) -> bool --- + + // Set the prismatic joint motor speed, typically in meters per second + PrismaticJoint_SetMotorSpeed :: proc(jointId: JointId, motorSpeed: f32) --- + + // Get the prismatic joint motor speed, typically in meters per second + PrismaticJoint_GetMotorSpeed :: proc(jointId: JointId) -> f32 --- + + // Set the prismatic joint maximum motor force, typically in newtons + PrismaticJoint_SetMaxMotorForce :: proc(jointId: JointId, force: f32) --- + + // Get the prismatic joint maximum motor force, typically in newtons + PrismaticJoint_GetMaxMotorForce :: proc(jointId: JointId) -> f32 --- + + // Get the prismatic joint current motor force, typically in newtons + PrismaticJoint_GetMotorForce :: proc(jointId: JointId) -> f32 --- + + /** + * @defgroup revolute_joint Revolute Joint + * @brief A revolute joint allows for relative rotation in the 2D plane with no relative translation. + * + * The revolute joint is probably the most common joint. It can be used for ragdolls and chains. + * Also called a *hinge* or *pin* joint. + */ + + // Create a revolute joint + // @see b2RevoluteJointDef for details + CreateRevoluteJoint :: proc(worldId: WorldId, #by_ptr def: RevoluteJointDef) -> JointId --- + + // Enable/disable the revolute joint spring + RevoluteJoint_EnableSpring :: proc(jointId: JointId, enableSpring: bool) --- + + // Set the revolute joint spring stiffness in Hertz + RevoluteJoint_SetSpringHertz :: proc(jointId: JointId, hertz: f32) --- + + // Get the revolute joint spring stiffness in Hertz + RevoluteJoint_GetSpringHertz :: proc(jointId: JointId) -> f32 --- + + // Set the revolute joint spring damping ratio, non-dimensional + RevoluteJoint_SetSpringDampingRatio :: proc(jointId: JointId, dampingRatio: f32) --- + + // Get the revolute joint spring damping ratio, non-dimensional + RevoluteJoint_GetSpringDampingRatio :: proc(jointId: JointId) -> f32 --- + + // Get the revolute joint current angle in radians relative to the reference angle + // @see b2RevoluteJointDef::referenceAngle + RevoluteJoint_GetAngle :: proc(jointId: JointId) -> f32 --- + + // Enable/disable the revolute joint limit + RevoluteJoint_EnableLimit :: proc(jointId: JointId, enableLimit: bool) --- + + // Is the revolute joint limit enabled? + RevoluteJoint_IsLimitEnabled :: proc(jointId: JointId) -> bool --- + + // Get the revolute joint lower limit in radians + RevoluteJoint_GetLowerLimit :: proc(jointId: JointId) -> f32 --- + + // Get the revolute joint upper limit in radians + RevoluteJoint_GetUpperLimit :: proc(jointId: JointId) -> f32 --- + + // Set the revolute joint limits in radians + RevoluteJoint_SetLimits :: proc(jointId: JointId, lower, upper: f32) --- + + // Enable/disable a revolute joint motor + RevoluteJoint_EnableMotor :: proc(jointId: JointId, enableMotor: bool) --- + + // Is the revolute joint motor enabled? + RevoluteJoint_IsMotorEnabled :: proc(jointId: JointId) -> bool --- + + // Set the revolute joint motor speed in radians per second + RevoluteJoint_SetMotorSpeed :: proc(jointId: JointId, motorSpeed: f32) --- + + // Get the revolute joint motor speed in radians per second + RevoluteJoint_GetMotorSpeed :: proc(jointId: JointId) -> f32 --- + + // Get the revolute joint current motor torque, typically in newton-meters + RevoluteJoint_GetMotorTorque :: proc(jointId: JointId) -> f32 --- + + // Set the revolute joint maximum motor torque, typically in newton-meters + RevoluteJoint_SetMaxMotorTorque :: proc(jointId: JointId, torque: f32) --- + + // Get the revolute joint maximum motor torque, typically in newton-meters + RevoluteJoint_GetMaxMotorTorque :: proc(jointId: JointId) -> f32 --- + + /**@}*/ + + /** + * @defgroup weld_joint Weld Joint + * @brief A weld joint fully constrains the relative transform between two bodies while allowing for springiness + * + * A weld joint constrains the relative rotation and translation between two bodies. Both rotation and translation + * can have damped springs. + * + * @note The accuracy of weld joint is limited by the accuracy of the solver. Long chains of weld joints may flex. + */ + + // Create a weld joint + // @see b2WeldJointDef for details + CreateWeldJoint :: proc(worldId: WorldId, #by_ptr def: WeldJointDef) -> JointId --- + + // Set the weld joint linear stiffness in Hertz. 0 is rigid. + WeldJoint_SetLinearHertz :: proc(jointId: JointId, hertz: f32) --- + + // Get the weld joint linear stiffness in Hertz + WeldJoint_GetLinearHertz :: proc(jointId: JointId) -> f32 --- + + // Set the weld joint linear damping ratio (non-dimensional) + WeldJoint_SetLinearDampingRatio :: proc(jointId: JointId, dampingRatio: f32) --- + + // Get the weld joint linear damping ratio (non-dimensional) + WeldJoint_GetLinearDampingRatio :: proc(jointId: JointId) -> f32 --- + + // Set the weld joint angular stiffness in Hertz. 0 is rigid. + WeldJoint_SetAngularHertz :: proc(jointId: JointId, hertz: f32) --- + + // Get the weld joint angular stiffness in Hertz + WeldJoint_GetAngularHertz :: proc(jointId: JointId) -> f32 --- + + // Set weld joint angular damping ratio, non-dimensional + WeldJoint_SetAngularDampingRatio :: proc(jointId: JointId, dampingRatio: f32) --- + + // Get the weld joint angular damping ratio, non-dimensional + WeldJoint_GetAngularDampingRatio :: proc(jointId: JointId) -> f32 --- + + /** + * @defgroup wheel_joint Wheel Joint + * The wheel joint can be used to simulate wheels on vehicles. + * + * The wheel joint restricts body B to move along a local axis in body A. Body B is free to + * rotate. Supports a linear spring, linear limits, and a rotational motor. + * + */ + + // Create a wheel joint + // @see b2WheelJointDef for details + CreateWheelJoint :: proc(worldId: WorldId, #by_ptr def: WheelJointDef) -> JointId --- + + // Enable/disable the wheel joint spring + WheelJoint_EnableSpring :: proc(jointId: JointId, enableSpring: bool) --- + + // Is the wheel joint spring enabled? + WheelJoint_IsSpringEnabled :: proc(jointId: JointId) -> bool --- + + // Set the wheel joint stiffness in Hertz + WheelJoint_SetSpringHertz :: proc(jointId: JointId, hertz: f32) --- + + // Get the wheel joint stiffness in Hertz + WheelJoint_GetSpringHertz :: proc(jointId: JointId) -> f32 --- + + // Set the wheel joint damping ratio, non-dimensional + WheelJoint_SetSpringDampingRatio :: proc(jointId: JointId, dampingRatio: f32) --- + + // Get the wheel joint damping ratio, non-dimensional + WheelJoint_GetSpringDampingRatio :: proc(jointId: JointId) -> f32 --- + + // Enable/disable the wheel joint limit + WheelJoint_EnableLimit :: proc(jointId: JointId, enableLimit: bool) --- + + // Is the wheel joint limit enabled? + WheelJoint_IsLimitEnabled :: proc(jointId: JointId) -> bool --- + + // Get the wheel joint lower limit + WheelJoint_GetLowerLimit :: proc(jointId: JointId) -> f32 --- + + // Get the wheel joint upper limit + WheelJoint_GetUpperLimit :: proc(jointId: JointId) -> f32 --- + + // Set the wheel joint limits + WheelJoint_SetLimits :: proc(jointId: JointId, lower, upper: f32) --- + + // Enable/disable the wheel joint motor + WheelJoint_EnableMotor :: proc(jointId: JointId, enableMotor: bool) --- + + // Is the wheel joint motor enabled? + WheelJoint_IsMotorEnabled :: proc(jointId: JointId) -> bool --- + + // Set the wheel joint motor speed in radians per second + WheelJoint_SetMotorSpeed :: proc(jointId: JointId, motorSpeed: f32) --- + + // Get the wheel joint motor speed in radians per second + WheelJoint_GetMotorSpeed :: proc(jointId: JointId) -> f32 --- + + // Set the wheel joint maximum motor torque, typically in newton-meters + WheelJoint_SetMaxMotorTorque :: proc(jointId: JointId, torque: f32) --- + + // Get the wheel joint maximum motor torque, typically in newton-meters + WheelJoint_GetMaxMotorTorque :: proc(jointId: JointId) -> f32 --- + + // Get the wheel joint current motor torque, typically in newton-meters + WheelJoint_GetMotorTorque :: proc(jointId: JointId) -> f32 --- +} + + + +IsValid :: proc{ + Float_IsValid, + Vec2_IsValid, + Rot_IsValid, + World_IsValid, + Body_IsValid, + Shape_IsValid, + Chain_IsValid, + Joint_IsValid, + + IsValidRay, +} \ No newline at end of file diff --git a/vendor/box2d/build_box2d.sh b/vendor/box2d/build_box2d.sh new file mode 100755 index 000000000..4fa64faa0 --- /dev/null +++ b/vendor/box2d/build_box2d.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -eu + +VERSION="3.0.0" +RELEASE="https://github.com/erincatto/box2d/archive/refs/tags/v$VERSION.tar.gz" + +cd "$(odin root)"/vendor/box2d + +curl -O -L "$RELEASE" +tar -xzvf "v$VERSION.tar.gz" + +cd "box2d-$VERSION" + +DISABLE_FLAGS="-DBOX2D_SAMPLES=OFF -DBOX2D_VALIDATE=OFF -DBOX2D_UNIT_TESTS=OFF" + +case "$(uname -s)" in +Darwin) + export MACOSX_DEPLOYMENT_TARGET="11" + + case "$(uname -m)" in + "x86_64" | "amd64") + rm -rf build + mkdir build + cmake $DISABLE_FLAGS -DBOX2D_AVX2=ON -DCMAKE_OSX_ARCHITECTURES=x86_64 -S . -B build + cmake --build build + cp build/src/libbox2d.a ../lib/box2d_darwin_amd64_avx2.a + + rm -rf build + mkdir build + cmake $DISABLE_FLAGS -DBOX2D_AVX2=OFF -DCMAKE_OSX_ARCHITECTURES=x86_64 -S . -B build + cmake --build build + cp build/src/libbox2d.a ../lib/box2d_darwin_amd64_sse2.a + ;; + *) + rm -rf build + mkdir build + cmake $DISABLE_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64 -S . -B build + cmake --build build + cp build/src/libbox2d.a ../lib/box2d_darwin_arm64.a + ;; + esac + ;; +*) + case "$(uname -m)" in + "x86_64" | "amd64") + rm -rf build + mkdir build + cmake $DISABLE_FLAGS -DBOX2D_AVX2=ON -S . -B build + cmake --build build + cp build/src/libbox2d.a ../lib/box2d_other_amd64_avx2.a + + rm -rf build + mkdir build + cmake $DISABLE_FLAGS -DBOX2D_AVX2=OFF -S . -B build + cmake --build build + cp build/src/libbox2d.a ../lib/box2d_other_amd64_sse2.a + ;; + *) + rm -rf build + mkdir build + cmake $DISABLE_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64 -S . -B build + cmake --build build + cp build/src/libbox2d.a ../lib/box2d_other.a + ;; + esac + ;; +esac + +cd .. + +rm -rf v3.0.0.tar.gz +rm -rf box2d-3.0.0 diff --git a/vendor/box2d/collision.odin b/vendor/box2d/collision.odin new file mode 100644 index 000000000..88f3b3a77 --- /dev/null +++ b/vendor/box2d/collision.odin @@ -0,0 +1,473 @@ +package vendor_box2d + +import "core:c" + + +// The maximum number of vertices on a convex polygon. Changing this affects performance even if you +// don't use more vertices. +maxPolygonVertices :: 8 + +// Low level ray-cast input data +RayCastInput :: struct { + // Start point of the ray cast + origin: Vec2, + + // Translation of the ray cast + translation: Vec2, + + // The maximum fraction of the translation to consider, typically 1 + maxFraction: f32, +} + +// Low level shape cast input in generic form. This allows casting an arbitrary point +// cloud wrap with a radius. For example, a circle is a single point with a non-zero radius. +// A capsule is two points with a non-zero radius. A box is four points with a zero radius. +ShapeCastInput :: struct { + // A point cloud to cast + points: [maxPolygonVertices]Vec2 `fmt:"v,count"`, + + // The number of points + count: i32, + + // The radius around the point cloud + radius: f32, + + // The translation of the shape cast + translation: Vec2, + + // The maximum fraction of the translation to consider, typically 1 + maxFraction: f32, +} + +// Low level ray-cast or shape-cast output data +CastOutput :: struct { + // The surface normal at the hit point + normal: Vec2, + + // The surface hit point + point: Vec2, + + // The fraction of the input translation at collision + fraction: f32, + + // The number of iterations used + iterations: i32, + + // Did the cast hit? + hit: bool, +} + +// This holds the mass data computed for a shape. +MassData :: struct { + // The mass of the shape, usually in kilograms. + mass: f32, + + // The position of the shape's centroid relative to the shape's origin. + center: Vec2, + + // The rotational inertia of the shape about the local origin. + rotationalInertia: f32, +} + +// A solid circle +Circle :: struct { + // The local center + center: Vec2, + + // The radius + radius: f32, +} + +// A solid capsule can be viewed as two semicircles connected +// by a rectangle. +Capsule :: struct { + // Local center of the first semicircle + center1: Vec2, + + // Local center of the second semicircle + center2: Vec2, + + // The radius of the semicircles + radius: f32, +} + +// A solid convex polygon. It is assumed that the interior of the polygon is to +// the left of each edge. +// Polygons have a maximum number of vertices equal to maxPolygonVertices. +// In most cases you should not need many vertices for a convex polygon. +// @warning DO NOT fill this out manually, instead use a helper function like +// b2MakePolygon or b2MakeBox. +Polygon :: struct { + // The polygon vertices + vertices: [maxPolygonVertices]Vec2 `fmt:"v,count"`, + + // The outward normal vectors of the polygon sides + normals: [maxPolygonVertices]Vec2 `fmt:"v,count"`, + + // The centroid of the polygon + centroid: Vec2, + + // The external radius for rounded polygons + radius: f32, + + // The number of polygon vertices + count: i32, +} + +// A line segment with two-sided collision. +Segment :: struct { + // The first point + point1: Vec2, + + // The second point + point2: Vec2, +} + +// A smooth line segment with one-sided collision. Only collides on the right side. +// Several of these are generated for a chain shape. +// ghost1 -> point1 -> point2 -> ghost2 +SmoothSegment :: struct { + // The tail ghost vertex + ghost1: Vec2, + + // The line segment + segment: Segment, + + // The head ghost vertex + ghost2: Vec2, + + // The owning chain shape index (internal usage only) + chainId: i32, +} + + +// A convex hull. Used to create convex polygons. +// @warning Do not modify these values directly, instead use b2ComputeHull() +Hull :: struct { + // The final points of the hull + points: [maxPolygonVertices]Vec2 `fmt:"v,count"`, + + // The number of points + count: i32, +} + +/** + * @defgroup distance Distance + * Functions for computing the distance between shapes. + * + * These are advanced functions you can use to perform distance calculations. There + * are functions for computing the closest points between shapes, doing linear shape casts, + * and doing rotational shape casts. The latter is called time of impact (TOI). + */ + +// Result of computing the distance between two line segments +SegmentDistanceResult :: struct { + // The closest point on the first segment + closest1: Vec2, + + // The closest point on the second segment + closest2: Vec2, + + // The barycentric coordinate on the first segment + fraction1: f32, + + // The barycentric coordinate on the second segment + fraction2: f32, + + // The squared distance between the closest points + distanceSquared: f32, +} + +// A distance proxy is used by the GJK algorithm. It encapsulates any shape. +DistanceProxy :: struct { + // The point cloud + points: [maxPolygonVertices]Vec2 `fmt:"v,count"`, + + // The number of points + count: i32, + + // The external radius of the point cloud + radius: f32, +} + +// Used to warm start b2Distance. Set count to zero on first call or +// use zero initialization. +DistanceCache :: struct { + // The number of stored simplex points + count: u16, + + // The cached simplex indices on shape A + indexA: [3]u8 `fmt:"v,count"`, + + // The cached simplex indices on shape B + indexB: [3]u8 `fmt:"v,count"`, +} + +emptyDistanceCache :: DistanceCache{} + +// Input for b2ShapeDistance +DistanceInput :: struct { + // The proxy for shape A + proxyA: DistanceProxy, + + // The proxy for shape B + proxyB: DistanceProxy, + + // The world transform for shape A + transformA: Transform, + + // The world transform for shape B + transformB: Transform, + + // Should the proxy radius be considered? + useRadii: bool, +} + +// Output for b2ShapeDistance +DistanceOutput :: struct { + pointA: Vec2, // Closest point on shapeA + pointB: Vec2, // Closest point on shapeB + distance: f32, // The final distance, zero if overlapped + iterations: i32, // Number of GJK iterations used + simplexCount: i32, // The number of simplexes stored in the simplex array +} + +// Simplex vertex for debugging the GJK algorithm +SimplexVertex :: struct { + wA: Vec2, // support point in proxyA + wB: Vec2, // support point in proxyB + w: Vec2, // wB - wA + a: f32, // barycentric coordinate for closest point + indexA: i32, // wA index + indexB: i32, // wB index +} + +// Simplex from the GJK algorithm +Simplex :: struct { + v1, v2, v3: SimplexVertex `fmt:"v,count"`, // vertices + count: i32, // number of valid vertices +} + +// Input parameters for b2ShapeCast +ShapeCastPairInput :: struct { + proxyA: DistanceProxy, // The proxy for shape A + proxyB: DistanceProxy, // The proxy for shape B + transformA: Transform, // The world transform for shape A + transformB: Transform, // The world transform for shape B + translationB: Vec2, // The translation of shape B + maxFraction: f32, // The fraction of the translation to consider, typically 1 +} + + +// This describes the motion of a body/shape for TOI computation. Shapes are defined with respect to the body origin, +// which may not coincide with the center of mass. However, to support dynamics we must interpolate the center of mass +// position. +Sweep :: struct { + localCenter: Vec2, // Local center of mass position + c1: Vec2, // Starting center of mass world position + c2: Vec2, // Ending center of mass world position + q1: Rot, // Starting world rotation + q2: Rot, // Ending world rotation +} + +// Input parameters for b2TimeOfImpact +TOIInput :: struct { + proxyA: DistanceProxy, // The proxy for shape A + proxyB: DistanceProxy, // The proxy for shape B + sweepA: Sweep, // The movement of shape A + sweepB: Sweep, // The movement of shape B + tMax: f32, // Defines the sweep interval [0, tMax] +} + +// Describes the TOI output +TOIState :: enum c.int { + Unknown, + Failed, + Overlapped, + Hit, + Separated, +} + +// Output parameters for b2TimeOfImpact. +TOIOutput :: struct { + state: TOIState, // The type of result + t: f32, // The time of the collision +} + + + +/** + * @defgroup collision Collision + * @brief Functions for colliding pairs of shapes + */ + +// A manifold point is a contact point belonging to a contact +// manifold. It holds details related to the geometry and dynamics +// of the contact points. +ManifoldPoint :: struct { + // Location of the contact point in world space. Subject to precision loss at large coordinates. + // @note Should only be used for debugging. + point: Vec2, + + // Location of the contact point relative to bodyA's origin in world space + // @note When used internally to the Box2D solver, these are relative to the center of mass. + anchorA: Vec2, + + // Location of the contact point relative to bodyB's origin in world space + anchorB: Vec2, + + // The separation of the contact point, negative if penetrating + separation: f32, + + // The impulse along the manifold normal vector. + normalImpulse: f32, + + // The friction impulse + tangentImpulse: f32, + + // The maximum normal impulse applied during sub-stepping + // todo not sure this is needed + maxNormalImpulse: f32, + + // Relative normal velocity pre-solve. Used for hit events. If the normal impulse is + // zero then there was no hit. Negative means shapes are approaching. + normalVelocity: f32, + + // Uniquely identifies a contact point between two shapes + id: u16, + + // Did this contact point exist the previous step? + persisted: bool, +} + +// A contact manifold describes the contact points between colliding shapes +Manifold :: struct { + // The manifold points, up to two are possible in 2D + points: [2]ManifoldPoint, + + // The unit normal vector in world space, points from shape A to bodyB + normal: Vec2, + + // The number of contacts points, will be 0, 1, or 2 + pointCount: i32, +} + + +/** + * @defgroup tree Dynamic Tree + * The dynamic tree is a binary AABB tree to organize and query large numbers of geometric objects + * + * Box2D uses the dynamic tree internally to sort collision shapes into a binary bounding volume hierarchy. + * This data structure may have uses in games for organizing other geometry data and may be used independently + * of Box2D rigid body simulation. + * + * A dynamic AABB tree broad-phase, inspired by Nathanael Presson's btDbvt. + * A dynamic tree arranges data in a binary tree to accelerate + * queries such as AABB queries and ray casts. Leaf nodes are proxies + * with an AABB. These are used to hold a user collision object, such as a reference to a b2Shape. + * Nodes are pooled and relocatable, so I use node indices rather than pointers. + * The dynamic tree is made available for advanced users that would like to use it to organize + * spatial game data besides rigid bodies. + * + * @note This is an advanced feature and normally not used by applications directly. + */ + +// The default category bit for a tree proxy. Used for collision filtering. +defaultCategoryBits :: 0x00000001 + +// Convenience mask bits to use when you don't need collision filtering and just want +// all results. +defaultMaskBits :: 0xFFFFFFFF + +// A node in the dynamic tree. This is private data placed here for performance reasons. +// 16 + 16 + 8 + pad(8) +TreeNode :: struct { + // The node bounding box + aabb: AABB, // 16 + + // Category bits for collision filtering + categoryBits: u32, // 4 + + using _: struct #raw_union { + // The node parent index + parent: i32, + + // The node freelist next index + next: i32, + }, // 4 + + // Child 1 index + child1: i32, // 4 + + // Child 2 index + child2: i32, // 4 + + // User data + // todo could be union with child index + userData: i32, // 4 + + // Leaf = 0, free node = -1 + height: i16, // 2 + + // Has the AABB been enlarged? + enlarged: bool, // 1 + + // Padding for clarity + _: [9]byte, +} + +// The dynamic tree structure. This should be considered private data. +// It is placed here for performance reasons. +DynamicTree :: struct { + // The tree nodes + nodes: [^]TreeNode `fmt"v,nodeCount"`, + + // The root index + root: i32, + + // The number of nodes + nodeCount: i32, + + // The allocated node space + nodeCapacity: i32, + + // Node free list + freeList: i32, + + // Number of proxies created + proxyCount: i32, + + // Leaf indices for rebuild + leafIndices: [^]i32, + + // Leaf bounding boxes for rebuild + leafBoxes: [^]AABB, + + // Leaf bounding box centers for rebuild + leafCenters: [^]Vec2, + + // Bins for sorting during rebuild + binIndices: [^]i32, + + // Allocated space for rebuilding + rebuildCapacity: i32, +} + +// This function receives proxies found in the AABB query. +// @return true if the query should continue +TreeQueryCallbackFcn :: #type proc "c" (proxyId: i32, userData: i32, ctx: rawptr) -> bool + +// This function receives clipped ray-cast input for a proxy. The function +// returns the new ray fraction. +// - return a value of 0 to terminate the ray-cast +// - return a value less than input->maxFraction to clip the ray +// - return a value of input->maxFraction to continue the ray cast without clipping +TreeShapeCastCallbackFcn :: #type proc "c" (#by_ptr input: ShapeCastInput, proxyId: i32, userData: i32, ctx: rawptr) -> f32 + + +// This function receives clipped raycast input for a proxy. The function +// returns the new ray fraction. +// - return a value of 0 to terminate the ray cast +// - return a value less than input->maxFraction to clip the ray +// - return a value of input->maxFraction to continue the ray cast without clipping +TreeRayCastCallbackFcn :: #type proc "c" (#by_ptr input: RayCastInput, proxyId: i32, userData: i32, ctx: rawptr) -> f32 diff --git a/vendor/box2d/id.odin b/vendor/box2d/id.odin new file mode 100644 index 000000000..211b8b79d --- /dev/null +++ b/vendor/box2d/id.odin @@ -0,0 +1,87 @@ +package vendor_box2d + +import "base:intrinsics" + +/** + * @defgroup id Ids + * These ids serve as handles to internal Box2D objects. + * These should be considered opaque data and passed by value. + * Include this header if you need the id types and not the whole Box2D API. + * All ids are considered null if initialized to zero. + * + * For example in Odin: + * + * @code{.odin} + * worldId := b2.WorldId{} + * @endcode + * + * This is considered null. + * + * @warning Do not use the internals of these ids. They are subject to change. Ids should be treated as opaque objects. + * @warning You should use ids to access objects in Box2D. Do not access files within the src folder. Such usage is unsupported. + */ + +/// World id references a world instance. This should be treated as an opaque handle. +WorldId :: struct { + index1: u16, + revision: u16, +} + +/// Body id references a body instance. This should be treated as an opaque handle. +BodyId :: struct { + index1: i32, + world0: u16, + revision: u16, +} + +/// Shape id references a shape instance. This should be treated as an opaque handle. +ShapeId :: struct { + index1: i32, + world0: u16, + revision: u16, +} + +/// Joint id references a joint instance. This should be treated as an opaque handle. +JointId :: struct { + index1: i32, + world0: u16, + revision: u16, +} + +/// Chain id references a chain instances. This should be treated as an opaque handle. +ChainId :: struct { + index1: i32, + world0: u16, + revision: u16, +} + +/// Use these to make your identifiers null. +/// You may also use zero initialization to get null. +nullWorldId :: WorldId{} +nullBodyId :: BodyId{} +nullShapeId :: ShapeId{} +nullJointId :: JointId{} +nullChainId :: ChainId{} + +/// Macro to determine if any id is null. +IS_NULL :: #force_inline proc "c" (id: $T) -> bool + where intrinsics.type_is_struct(T), + intrinsics.type_has_field(T, "index1") { + return id.index1 == 0 +} + +/// Macro to determine if any id is non-null. +IS_NON_NULL :: #force_inline proc "c" (id: $T) -> bool + where intrinsics.type_is_struct(T), + intrinsics.type_has_field(T, "index1") { + return id.index1 != 0 +} + +/// Compare two ids for equality. Doesn't work for b2WorldId. +ID_EQUALS :: #force_inline proc "c" (id1, id2: $T) -> bool + where intrinsics.type_is_struct(T), + intrinsics.type_has_field(T, "index1"), + intrinsics.type_has_field(T, "world0"), + intrinsics.type_has_field(T, "revision") { + return id1.index1 == id2.index1 && id1.world0 == id2.world0 && id1.revision == id2.revision +} diff --git a/vendor/box2d/lib/box2d_darwin_amd64_avx2.a b/vendor/box2d/lib/box2d_darwin_amd64_avx2.a new file mode 100644 index 000000000..2e2b72cb1 Binary files /dev/null and b/vendor/box2d/lib/box2d_darwin_amd64_avx2.a differ diff --git a/vendor/box2d/lib/box2d_darwin_amd64_sse2.a b/vendor/box2d/lib/box2d_darwin_amd64_sse2.a new file mode 100644 index 000000000..cd6cb1ca8 Binary files /dev/null and b/vendor/box2d/lib/box2d_darwin_amd64_sse2.a differ diff --git a/vendor/box2d/lib/box2d_darwin_arm64.a b/vendor/box2d/lib/box2d_darwin_arm64.a new file mode 100644 index 000000000..5723290b7 Binary files /dev/null and b/vendor/box2d/lib/box2d_darwin_arm64.a differ diff --git a/vendor/box2d/lib/box2d_windows_amd64_avx2.lib b/vendor/box2d/lib/box2d_windows_amd64_avx2.lib new file mode 100644 index 000000000..cea3f678d Binary files /dev/null and b/vendor/box2d/lib/box2d_windows_amd64_avx2.lib differ diff --git a/vendor/box2d/lib/box2d_windows_amd64_sse2.lib b/vendor/box2d/lib/box2d_windows_amd64_sse2.lib new file mode 100644 index 000000000..1ba62c76b Binary files /dev/null and b/vendor/box2d/lib/box2d_windows_amd64_sse2.lib differ diff --git a/vendor/box2d/math_functions.odin b/vendor/box2d/math_functions.odin new file mode 100644 index 000000000..2294a515c --- /dev/null +++ b/vendor/box2d/math_functions.odin @@ -0,0 +1,522 @@ +package vendor_box2d + +import "core:c" +import "core:math" + +pi :: 3.14159265359 + +Vec2 :: [2]f32 +Rot :: struct { + c, s: f32, // cosine and sine +} + +Transform :: struct { + p: Vec2, + q: Rot, +} + +Mat22 :: matrix[2, 2]f32 +AABB :: struct { + lowerBound: Vec2, + upperBound: Vec2, +} + +Vec2_zero :: Vec2{0, 0} +Rot_identity :: Rot{1, 0} +Transform_identity :: Transform{{0, 0}, {1, 0}} +Mat22_zero :: Mat22{0, 0, 0, 0} + + +// @return the minimum of two floats +@(deprecated="Prefer the built-in 'min(a, b)'", require_results) +MinFloat :: proc "c" (a, b: f32) -> f32 { + return min(a, b) +} + +// @return the maximum of two floats +@(deprecated="Prefer the built-in 'max(a, b)'", require_results) +MaxFloat :: proc "c" (a, b: f32) -> f32 { + return max(a, b) +} + +// @return the absolute value of a float +@(deprecated="Prefer the built-in 'abs(a)'", require_results) +AbsFloat :: proc "c" (a: f32) -> f32 { + return abs(a) +} + +// @return a f32 clamped between a lower and upper bound +@(deprecated="Prefer the built-in 'clamp(a, lower, upper)'", require_results) +ClampFloat :: proc "c" (a, lower, upper: f32) -> f32 { + return clamp(a, lower, upper) +} + +// @return the minimum of two integers +@(deprecated="Prefer the built-in 'min(a, b)'", require_results) +MinInt :: proc "c" (a, b: c.int) -> c.int { + return min(a, b) +} + +// @return the maximum of two integers +@(deprecated="Prefer the built-in 'max(a, b)'", require_results) +MaxInt :: proc "c" (a, b: c.int) -> c.int { + return max(a, b) +} + +// @return the absolute value of an integer +@(deprecated="Prefer the built-in 'abs(a)'", require_results) +AbsInt :: proc "c" (a: c.int) -> c.int { + return abs(a) +} + +// @return an integer clamped between a lower and upper bound +@(deprecated="Prefer the built-in 'clamp(a, lower, upper)'", require_results) +ClampInt :: proc "c" (a, lower, upper: c.int) -> c.int { + return clamp(a, lower, upper) +} + +// Vector dot product +@(require_results) +Dot :: proc "c" (a, b: Vec2) -> f32 { + return a.x * b.x + a.y * b.y +} + +// Vector cross product. In 2D this yields a scalar. +@(require_results) +Cross :: proc "c" (a, b: Vec2) -> f32 { + return a.x * b.y - a.y * b.x +} + +// Perform the cross product on a vector and a scalar. In 2D this produces a vector. +@(require_results) +CrossVS :: proc "c" (v: Vec2, s: f32) -> Vec2 { + return {s * v.y, -s * v.x} +} + +// Perform the cross product on a scalar and a vector. In 2D this produces a vector. +@(require_results) +CrossSV :: proc "c" (s: f32, v: Vec2) -> Vec2 { + return {-s * v.y, s * v.x} +} + +// Get a left pointing perpendicular vector. Equivalent to b2CrossSV(1, v) +@(require_results) +LeftPerp :: proc "c" (v: Vec2) -> Vec2 { + return {-v.y, v.x} +} + +// Get a right pointing perpendicular vector. Equivalent to b2CrossVS(v, 1) +@(require_results) +RightPerp :: proc "c" (v: Vec2) -> Vec2 { + return {v.y, -v.x} +} + +// Vector addition +@(deprecated="Prefer 'a + b'", require_results) +Add :: proc "c" (a, b: Vec2) -> Vec2 { + return a + b +} + +// Vector subtraction +@(deprecated="Prefer 'a - b'", require_results) +Sub :: proc "c" (a, b: Vec2) -> Vec2 { + return a - b +} + +// Vector negation +@(deprecated="Prefer '-a'", require_results) +Neg :: proc "c" (a: Vec2) -> Vec2 { + return -a +} + +// Vector linear interpolation +// https://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/ +@(require_results) +Lerp :: proc "c" (a, b: Vec2, t: f32) -> Vec2 { + return {(1 - t) * a.x + t * b.x, (1 - t) * a.y + t * b.y} +} + +// Component-wise multiplication +@(deprecated="Prefer 'a * b'", require_results) +Mul :: proc "c" (a, b: Vec2) -> Vec2 { + return a * b +} + +// Multiply a scalar and vector +@(deprecated="Prefer 's * v'", require_results) +MulSV :: proc "c" (s: f32, v: Vec2) -> Vec2 { + return s * v +} + +// a + s * b +@(deprecated="Prefer 'a + s * b'", require_results) +MulAdd :: proc "c" (a: Vec2, s: f32, b: Vec2) -> Vec2 { + return a + s * b +} + +// a - s * b +@(deprecated="Prefer 'a - s * b'", require_results) +MulSub :: proc "c" (a: Vec2, s: f32, b: Vec2) -> Vec2 { + return a - s * b +} + +// Component-wise absolute vector +@(require_results) +Abs :: proc "c" (a: Vec2) -> (b: Vec2) { + b.x = abs(a.x) + b.y = abs(a.y) + return +} + +// Component-wise minimum vector +@(require_results) +Min :: proc "c" (a, b: Vec2) -> (c: Vec2) { + c.x = min(a.x, b.x) + c.y = min(a.y, b.y) + return +} + +// Component-wise maximum vector +@(require_results) +Max :: proc "c" (a, b: Vec2) -> (c: Vec2) { + c.x = max(a.x, b.x) + c.y = max(a.y, b.y) + return +} + +// Component-wise clamp vector v into the range [a, b] +@(require_results) +Clamp :: proc "c" (v: Vec2, a, b: Vec2) -> (c: Vec2) { + c.x = clamp(v.x, a.x, b.x) + c.y = clamp(v.y, a.y, b.y) + return +} + +// Get the length of this vector (the norm) +@(require_results) +Length :: proc "c" (v: Vec2) -> f32 { + return math.sqrt(v.x * v.x + v.y * v.y) +} + +// Get the length squared of this vector +@(require_results) +LengthSquared :: proc "c" (v: Vec2) -> f32 { + return v.x * v.x + v.y * v.y +} + +// Get the distance between two points +@(require_results) +Distance :: proc "c" (a, b: Vec2) -> f32 { + dx := b.x - a.x + dy := b.y - a.y + return math.sqrt(dx * dx + dy * dy) +} + +// Get the distance squared between points +@(require_results) +DistanceSquared :: proc "c" (a, b: Vec2) -> f32 { + c := Vec2{b.x - a.x, b.y - a.y} + return c.x * c.x + c.y * c.y +} + +// Make a rotation using an angle in radians +@(require_results) +MakeRot :: proc "c" (angle: f32) -> Rot { + // todo determinism + return {math.cos(angle), math.sin(angle)} +} + +// Normalize rotation +@(require_results) +NormalizeRot :: proc "c" (q: Rot) -> Rot { + mag := math.sqrt(q.s * q.s + q.c * q.c) + invMag := f32(mag > 0.0 ? 1.0 / mag : 0.0) + return {q.c * invMag, q.s * invMag} +} + +// Is this rotation normalized? +@(require_results) +IsNormalized :: proc "c" (q: Rot) -> bool { + // larger tolerance due to failure on mingw 32-bit + qq := q.s * q.s + q.c * q.c + return 1.0 - 0.0006 < qq && qq < 1 + 0.0006 +} + +// Normalized linear interpolation +// https://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/ +@(require_results) +NLerp :: proc "c" (q1: Rot, q2: Rot, t: f32) -> Rot { + omt := 1 - t + return NormalizeRot({ + omt * q1.c + t * q2.c, + omt * q1.s + t * q2.s, + }) +} + +// Integration rotation from angular velocity +// @param q1 initial rotation +// @param deltaAngle the angular displacement in radians +@(require_results) +IntegrateRotation :: proc "c" (q1: Rot, deltaAngle: f32) -> Rot { + // dc/dt = -omega * sin(t) + // ds/dt = omega * cos(t) + // c2 = c1 - omega * h * s1 + // s2 = s1 + omega * h * c1 + q2 := Rot{q1.c - deltaAngle * q1.s, q1.s + deltaAngle * q1.c} + mag := math.sqrt(q2.s * q2.s + q2.c * q2.c) + invMag := f32(mag > 0.0 ? 1 / mag : 0.0) + return {q2.c * invMag, q2.s * invMag} +} + +// Compute the angular velocity necessary to rotate between two rotations over a give time +// @param q1 initial rotation +// @param q2 final rotation +// @param inv_h inverse time step +@(require_results) +ComputeAngularVelocity :: proc "c" (q1: Rot, q2: Rot, inv_h: f32) -> f32 { + // ds/dt = omega * cos(t) + // dc/dt = -omega * sin(t) + // s2 = s1 + omega * h * c1 + // c2 = c1 - omega * h * s1 + + // omega * h * s1 = c1 - c2 + // omega * h * c1 = s2 - s1 + // omega * h = (c1 - c2) * s1 + (s2 - s1) * c1 + // omega * h = s1 * c1 - c2 * s1 + s2 * c1 - s1 * c1 + // omega * h = s2 * c1 - c2 * s1 = sin(a2 - a1) ~= a2 - a1 for small delta + omega := inv_h * (q2.s * q1.c - q2.c * q1.s) + return omega +} + +// Get the angle in radians in the range [-pi, pi] +@(require_results) +Rot_GetAngle :: proc "c" (q: Rot) -> f32 { + // todo determinism + return math.atan2(q.s, q.c) +} + +// Get the x-axis +@(require_results) +Rot_GetXAxis :: proc "c" (q: Rot) -> Vec2 { + return {q.c, q.s} +} + +// Get the y-axis +@(require_results) +Rot_GetYAxis :: proc "c" (q: Rot) -> Vec2 { + return {-q.s, q.c} +} + +// Multiply two rotations: q * r +@(require_results) +MulRot :: proc "c" (q, r: Rot) -> (qr: Rot) { + // [qc -qs] * [rc -rs] = [qc*rc-qs*rs -qc*rs-qs*rc] + // [qs qc] [rs rc] [qs*rc+qc*rs -qs*rs+qc*rc] + // s(q + r) = qs * rc + qc * rs + // c(q + r) = qc * rc - qs * rs + qr.s = q.s * r.c + q.c * r.s + qr.c = q.c * r.c - q.s * r.s + return +} + +// Transpose multiply two rotations: qT * r +@(require_results) +InvMulRot :: proc "c" (q, r: Rot) -> (qr: Rot) { + // [ qc qs] * [rc -rs] = [qc*rc+qs*rs -qc*rs+qs*rc] + // [-qs qc] [rs rc] [-qs*rc+qc*rs qs*rs+qc*rc] + // s(q - r) = qc * rs - qs * rc + // c(q - r) = qc * rc + qs * rs + qr.s = q.c * r.s - q.s * r.c + qr.c = q.c * r.c + q.s * r.s + return +} + +// relative angle between b and a (rot_b * inv(rot_a)) +@(require_results) +RelativeAngle :: proc "c" (b, a: Rot) -> f32 { + // sin(b - a) = bs * ac - bc * as + // cos(b - a) = bc * ac + bs * as + s := b.s * a.c - b.c * a.s + c := b.c * a.c + b.s * a.s + return math.atan2(s, c) +} + +// Convert an angle in the range [-2*pi, 2*pi] into the range [-pi, pi] +@(require_results) +UnwindAngle :: proc "c" (angle: f32) -> f32 { + if angle < -pi { + return angle + 2.0 * pi + } else if angle > pi { + return angle - 2.0 * pi + } + return angle +} + +// Rotate a vector +@(require_results) +RotateVector :: proc "c" (q: Rot, v: Vec2) -> Vec2 { + return {q.c * v.x - q.s * v.y, q.s * v.x + q.c * v.y} +} + +// Inverse rotate a vector +@(require_results) +InvRotateVector :: proc "c" (q: Rot, v: Vec2) -> Vec2 { + return {q.c * v.x + q.s * v.y, -q.s * v.x + q.c * v.y} +} + +// Transform a point (e.g. local space to world space) +@(require_results) +TransformPoint :: proc "c" (t: Transform, p: Vec2) -> Vec2 { + x := (t.q.c * p.x - t.q.s * p.y) + t.p.x + y := (t.q.s * p.x + t.q.c * p.y) + t.p.y + return {x, y} +} + +// Inverse transform a point (e.g. world space to local space) +@(require_results) +InvTransformPoint :: proc "c" (t: Transform, p: Vec2) -> Vec2 { + vx := p.x - t.p.x + vy := p.y - t.p.y + return {t.q.c * vx + t.q.s * vy, -t.q.s * vx + t.q.c * vy} +} + +// v2 = A.q.Rot(B.q.Rot(v1) + B.p) + A.p +// = (A.q * B.q).Rot(v1) + A.q.Rot(B.p) + A.p +@(require_results) +MulTransforms :: proc "c" (A, B: Transform) -> (C: Transform) { + C.q = MulRot(A.q, B.q) + C.p = RotateVector(A.q, B.p) + A.p + return +} + +// v2 = A.q' * (B.q * v1 + B.p - A.p) +// = A.q' * B.q * v1 + A.q' * (B.p - A.p) +@(require_results) +InvMulTransforms :: proc "c" (A, B: Transform) -> (C: Transform) { + C.q = InvMulRot(A.q, B.q) + C.p = InvRotateVector(A.q, B.p-A.p) + return +} + +// Multiply a 2-by-2 matrix times a 2D vector +@(deprecated="Prefer 'A * v'", require_results) +MulMV :: proc "c" (A: Mat22, v: Vec2) -> Vec2 { + return A * v +} + +// Get the inverse of a 2-by-2 matrix +@(require_results) +GetInverse22 :: proc "c" (A: Mat22) -> Mat22 { + a := A[0, 0] + b := A[0, 1] + c := A[1, 0] + d := A[1, 1] + det := a * d - b * c + if det != 0.0 { + det = 1 / det + } + + return Mat22{ + det * d, -det * b, + -det * c, det * a, + } +} + +// Solve A * x = b, where b is a column vector. This is more efficient +// than computing the inverse in one-shot cases. +@(require_results) +Solve22 :: proc "c" (A: Mat22, b: Vec2) -> Vec2 { + a11 := A[0, 0] + a12 := A[0, 1] + a21 := A[1, 0] + a22 := A[1, 1] + det := a11 * a22 - a12 * a21 + if det != 0.0 { + det = 1 / det + } + return {det * (a22 * b.x - a12 * b.y), det * (a11 * b.y - a21 * b.x)} +} + +// Does a fully contain b +@(require_results) +AABB_Contains :: proc "c" (a, b: AABB) -> bool { + (a.lowerBound.x <= b.lowerBound.x) or_return + (a.lowerBound.y <= b.lowerBound.y) or_return + (b.upperBound.x <= a.upperBound.x) or_return + (b.upperBound.y <= a.upperBound.y) or_return + return true +} + +// Get the center of the AABB. +@(require_results) +AABB_Center :: proc "c" (a: AABB) -> Vec2 { + return {0.5 * (a.lowerBound.x + a.upperBound.x), 0.5 * (a.lowerBound.y + a.upperBound.y)} +} + +// Get the extents of the AABB (half-widths). +@(require_results) +AABB_Extents :: proc "c" (a: AABB) -> Vec2 { + return {0.5 * (a.upperBound.x - a.lowerBound.x), 0.5 * (a.upperBound.y - a.lowerBound.y)} +} + +// Union of two AABBs +@(require_results) +AABB_Union :: proc "c" (a, b: AABB) -> (c: AABB) { + c.lowerBound.x = min(a.lowerBound.x, b.lowerBound.x) + c.lowerBound.y = min(a.lowerBound.y, b.lowerBound.y) + c.upperBound.x = max(a.upperBound.x, b.upperBound.x) + c.upperBound.y = max(a.upperBound.y, b.upperBound.y) + return +} + +@(require_results) +Float_IsValid :: proc "c" (a: f32) -> bool { + math.is_nan(a) or_return + math.is_inf(a) or_return + return true +} + +@(require_results) +Vec2_IsValid :: proc "c" (v: Vec2) -> bool { + (math.is_nan(v.x) || math.is_nan(v.y)) or_return + (math.is_inf(v.x) || math.is_inf(v.y)) or_return + return true +} + +@(require_results) +Rot_IsValid :: proc "c" (q: Rot) -> bool { + (math.is_nan(q.s) || math.is_nan(q.c)) or_return + (math.is_inf(q.s) || math.is_inf(q.c)) or_return + return IsNormalized(q) +} + +@(require_results) +Normalize :: proc "c" (v: Vec2) -> Vec2 { + length := Length(v) + if length < 1e-23 { + return Vec2_zero + } + invLength := 1 / length + return invLength * v +} + +@(require_results) +NormalizeChecked :: proc "odin" (v: Vec2) -> Vec2 { + length := Length(v) + if length < 1e-23 { + panic("zero-length Vec2") + } + invLength := 1 / length + return invLength * v +} + +@(require_results) +GetLengthAndNormalize :: proc "c" (v: Vec2) -> (length: f32, vn: Vec2) { + length = Length(v) + if length < 1e-23 { + return + } + invLength := 1 / length + vn = invLength * v + return +} diff --git a/vendor/box2d/types.odin b/vendor/box2d/types.odin new file mode 100644 index 000000000..1ea7cb65a --- /dev/null +++ b/vendor/box2d/types.odin @@ -0,0 +1,1231 @@ +package vendor_box2d + +import "core:c" + +// Task interface +// This is prototype for a Box2D task. Your task system is expected to invoke the Box2D task with these arguments. +// The task spans a range of the parallel-for: [startIndex, endIndex) +// The worker index must correctly identify each worker in the user thread pool, expected in [0, workerCount). +// A worker must only exist on only one thread at a time and is analogous to the thread index. +// The task context is the context pointer sent from Box2D when it is enqueued. +// The startIndex and endIndex are expected in the range [0, itemCount) where itemCount is the argument to b2EnqueueTaskCallback +// below. Box2D expects startIndex < endIndex and will execute a loop like this: +// +// @code{.odin} +// for i in startIndex ..< endIndex { +// DoWork() +// } +// @endcode +// @ingroup world +TaskCallback :: #type proc "c" (startIndex, endIndex: i32, workerIndex: u32, taskContext: rawptr) + + +// These functions can be provided to Box2D to invoke a task system. These are designed to work well with enkiTS. +// Returns a pointer to the user's task object. May be nullptr. A nullptr indicates to Box2D that the work was executed +// serially within the callback and there is no need to call b2FinishTaskCallback. +// The itemCount is the number of Box2D work items that are to be partitioned among workers by the user's task system. +// This is essentially a parallel-for. The minRange parameter is a suggestion of the minimum number of items to assign +// per worker to reduce overhead. For example, suppose the task is small and that itemCount is 16. A minRange of 8 suggests +// that your task system should split the work items among just two workers, even if you have more available. +// In general the range [startIndex, endIndex) send to TaskCallback should obey: +// endIndex - startIndex >= minRange +// The exception of course is when itemCount < minRange. +// @ingroup world +EnqueueTaskCallback :: #type proc "c" (task: TaskCallback, itemCount: i32, minRange: i32, taskContext: rawptr, userContext: rawptr) -> rawptr + +// Finishes a user task object that wraps a Box2D task. +// @ingroup world +FinishTaskCallback :: #type proc "c" (userTask: rawptr, userContext: rawptr) + +// Result from b2World_RayCastClosest +// @ingroup world +RayResult :: struct { + shapeId: ShapeId, + point: Vec2, + normal: Vec2, + fraction: f32, + hit: bool, +} + +// World definition used to create a simulation world. +// Must be initialized using b2DefaultWorldDef(). +// @ingroup world +WorldDef :: struct { + // Gravity vector. Box2D has no up-vector defined. + gravity: Vec2, + + // Restitution velocity threshold, usually in m/s. Collisions above this + // speed have restitution applied (will bounce). + restitutionThreshold: f32, + + // This parameter controls how fast overlap is resolved and has units of meters per second + contactPushoutVelocity: f32, + + // Threshold velocity for hit events. Usually meters per second. + hitEventThreshold: f32, + + // Contact stiffness. Cycles per second. + contactHertz: f32, + + // Contact bounciness. Non-dimensional. + contactDampingRatio: f32, + + // Joint stiffness. Cycles per second. + jointHertz: f32, + + // Joint bounciness. Non-dimensional. + jointDampingRatio: f32, + + // Can bodies go to sleep to improve performance + enableSleep: bool, + + // Enable continuous collision + enableContinous: bool, + + // Number of workers to use with the provided task system. Box2D performs best when using only + // performance cores and accessing a single L2 cache. Efficiency cores and hyper-threading provide + // little benefit and may even harm performance. + workerCount: i32, + + // Function to spawn tasks + enqueueTask: EnqueueTaskCallback, + + // Function to finish a task + finishTask: FinishTaskCallback, + + // User context that is provided to enqueueTask and finishTask + userTaskContext: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + + + +// The body simulation type. +// Each body is one of these three types. The type determines how the body behaves in the simulation. +// @ingroup body +BodyType :: enum c.int { + // zero mass, zero velocity, may be manually moved + staticBody = 0, + + // zero mass, velocity set by user, moved by solver + kinematicBody = 1, + + // positive mass, velocity determined by forces, moved by solver + dynamicBody = 2, + +} + +// number of body types +bodyTypeCount :: len(BodyType) + +// A body definition holds all the data needed to construct a rigid body. +// You can safely re-use body definitions. Shapes are added to a body after construction. +// Body definitions are temporary objects used to bundle creation parameters. +// Must be initialized using b2DefaultBodyDef(). +// @ingroup body +BodyDef :: struct { + // The body type: static, kinematic, or dynamic. + type: BodyType, + + // The initial world position of the body. Bodies should be created with the desired position. + // @note Creating bodies at the origin and then moving them nearly doubles the cost of body creation, especially + // if the body is moved after shapes have been added. + position: Vec2, + + // The initial world rotation of the body. Use b2MakeRot() if you have an angle. + rotation: Rot, + + // The initial linear velocity of the body's origin. Typically in meters per second. + linearVelocity: Vec2, + + // The initial angular velocity of the body. Radians per second. + angularVelocity: f32, + + // Linear damping is use to reduce the linear velocity. The damping parameter + // can be larger than 1 but the damping effect becomes sensitive to the + // time step when the damping parameter is large. + // Generally linear damping is undesirable because it makes objects move slowly + // as if they are f32ing. + linearDamping: f32, + + // Angular damping is use to reduce the angular velocity. The damping parameter + // can be larger than 1.0f but the damping effect becomes sensitive to the + // time step when the damping parameter is large. + // Angular damping can be use slow down rotating bodies. + angularDamping: f32, + + // Scale the gravity applied to this body. Non-dimensional. + gravityScale: f32, + + // Sleep velocity threshold, default is 0.05 meter per second + sleepThreshold: f32, + + // Use this to store application specific body data. + userData: rawptr, + + // Set this flag to false if this body should never fall asleep. + enableSleep: bool, + + // Is this body initially awake or sleeping? + isAwake: bool, + + // Should this body be prevented from rotating? Useful for characters. + fixedRotation: bool, + + // Treat this body as high speed object that performs continuous collision detection + // against dynamic and kinematic bodies, but not other bullet bodies. + // @warning Bullets should be used sparingly. They are not a solution for general dynamic-versus-dynamic + // continuous collision. They may interfere with joint constraints. + isBullet: bool, + + // Used to disable a body. A disabled body does not move or collide. + isEnabled: bool, + + // Automatically compute mass and related properties on this body from shapes. + // Triggers whenever a shape is add/removed/changed. Default is true. + automaticMass: bool, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// This is used to filter collision on shapes. It affects shape-vs-shape collision +// and shape-versus-query collision (such as b2World_CastRay). +// @ingroup shape +Filter :: struct { + // The collision category bits. Normally you would just set one bit. The category bits should + // represent your application object types. For example: + // @code{.odin} + // My_Categories :: enum u32 { + // Static = 0x00000001, + // Dynamic = 0x00000002, + // Debris = 0x00000004, + // Player = 0x00000008, + // // etc + // }; + // @endcode + // Or use a bit_set. + categoryBits: u32, + + // The collision mask bits. This states the categories that this + // shape would accept for collision. + // For example, you may want your player to only collide with static objects + // and other players. + // @code{.odin} + // maskBits = u32(My_Categories.Static | My_Categories.Player); + // @endcode + maskBits: u32, + + // Collision groups allow a certain group of objects to never collide (negative) + // or always collide (positive). A group index of zero has no effect. Non-zero group filtering + // always wins against the mask bits. + // For example, you may want ragdolls to collide with other ragdolls but you don't want + // ragdoll self-collision. In this case you would give each ragdoll a unique negative group index + // and apply that group index to all shapes on the ragdoll. + groupIndex: i32, +} + + +// The query filter is used to filter collisions between queries and shapes. For example, +// you may want a ray-cast representing a projectile to hit players and the static environment +// but not debris. +// @ingroup shape +QueryFilter :: struct { + // The collision category bits of this query. Normally you would just set one bit. + categoryBits: u32, + + // The collision mask bits. This states the shape categories that this + // query would accept for collision. + maskBits: u32, +} + + +// Shape type +// @ingroup shape +ShapeType :: enum c.int { + // A circle with an offset + circleShape, + + // A capsule is an extruded circle + capsuleShape, + + // A line segment + segmentShape, + + // A convex polygon + polygonShape, + + // A smooth segment owned by a chain shape + smoothSegmentShape, +} + +// The number of shape types +shapeTypeCount :: len(ShapeType) + +// Used to create a shape. +// This is a temporary object used to bundle shape creation parameters. You may use +// the same shape definition to create multiple shapes. +// Must be initialized using b2DefaultShapeDef(). +// @ingroup shape +ShapeDef :: struct { + // Use this to store application specific shape data. + userData: rawptr, + + // The Coulomb (dry) friction coefficient, usually in the range [0,1]. + friction: f32, + + // The restitution (bounce) usually in the range [0,1]. + restitution: f32, + + // The density, usually in kg/m^2. + density: f32, + + // Collision filtering data. + filter: Filter, + + // Custom debug draw color. + customColor: u32, + + // A sensor shape generates overlap events but never generates a collision response. + isSensor: bool, + + // Enable sensor events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. + enableSensorEvents: bool, + + // Enable contact events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. + enableContactEvents: bool, + + // Enable hit events for this shape. Only applies to kinematic and dynamic bodies. Ignored for sensors. + enableHitEvents: bool, + + // Enable pre-solve contact events for this shape. Only applies to dynamic bodies. These are expensive + // and must be carefully handled due to threading. Ignored for sensors. + enablePreSolveEvents: bool, + + // Normally shapes on static bodies don't invoke contact creation when they are added to the world. This overrides + // that behavior and causes contact creation. This significantly slows down static body creation which can be important + // when there are many static shapes. + forceContactCreation: bool, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// Used to create a chain of edges. This is designed to eliminate ghost collisions with some limitations. +// - chains are one-sided +// - chains have no mass and should be used on static bodies +// - chains have a counter-clockwise winding order +// - chains are either a loop or open +// - a chain must have at least 4 points +// - the distance between any two points must be greater than b2_linearSlop +// - a chain shape should not self intersect (this is not validated) +// - an open chain shape has NO COLLISION on the first and final edge +// - you may overlap two open chains on their first three and/or last three points to get smooth collision +// - a chain shape creates multiple smooth edges shapes on the body +// https://en.wikipedia.org/wiki/Polygonal_chain +// Must be initialized using b2DefaultChainDef(). +// @warning Do not use chain shapes unless you understand the limitations. This is an advanced feature. +// @ingroup shape +ChainDef :: struct { + // Use this to store application specific shape data. + userData: rawptr, + + // An array of at least 4 points. These are cloned and may be temporary. + points: [^]Vec2 `fmt:"v,count"`, + + // The point count, must be 4 or more. + count: i32, + + // The friction coefficient, usually in the range [0,1]. + friction: f32, + + // The restitution (elasticity) usually in the range [0,1]. + restitution: f32, + + // Contact filtering data. + filter: Filter, + + // Indicates a closed chain formed by connecting the first and last points + isLoop: bool, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +//! @cond +// Profiling data. Times are in milliseconds. +Profile :: struct { + step: f32, + pairs: f32, + collide: f32, + solve: f32, + buildIslands: f32, + solveConstraints: f32, + prepareTasks: f32, + solverTasks: f32, + prepareConstraints: f32, + integrateVelocities: f32, + warmStart: f32, + solveVelocities: f32, + integratePositions: f32, + relaxVelocities: f32, + applyRestitution: f32, + storeImpulses: f32, + finalizeBodies: f32, + splitIslands: f32, + sleepIslands: f32, + hitEvents: f32, + broadphase: f32, + continuous: f32, +} + +// Counters that give details of the simulation size. +Counters :: struct { + staticBodyCount: i32, + bodyCount: i32, + shapeCount: i32, + contactCount: i32, + jointCount: i32, + islandCount: i32, + stackUsed: i32, + staticTreeHeight: i32, + treeHeight: i32, + byteCount: i32, + taskCount: i32, + colorCounts: [12]i32, +} +//! @endcond + +// Joint type enumeration +// +// This is useful because all joint types use b2JointId and sometimes you +// want to get the type of a joint. +// @ingroup joint +JointType :: enum c.int { + distanceJoint, + motorJoint, + mouseJoint, + prismaticJoint, + revoluteJoint, + weldJoint, + wheelJoint, +} + +// Distance joint definition +// +// This requires defining an anchor point on both +// bodies and the non-zero distance of the distance joint. The definition uses +// local anchor points so that the initial configuration can violate the +// constraint slightly. This helps when saving and loading a game. +// @ingroup distance_joint +DistanceJointDef :: struct { + // The first attached body + bodyIdA: BodyId, + + // The second attached body + bodyIdB: BodyId, + + // The local anchor point relative to bodyA's origin + localAnchorA: Vec2, + + // The local anchor point relative to bodyB's origin + localAnchorB: Vec2, + + // The rest length of this joint. Clamped to a stable minimum value. + length: f32, + + // Enable the distance constraint to behave like a spring. If false + // then the distance joint will be rigid, overriding the limit and motor. + enableSpring: bool, + + // The spring linear stiffness Hertz, cycles per second + hertz: f32, + + // The spring linear damping ratio, non-dimensional + dampingRatio: f32, + + // Enable/disable the joint limit + enableLimit: bool, + + // Minimum length. Clamped to a stable minimum value. + minLength: f32, + + // Maximum length. Must be greater than or equal to the minimum length. + maxLength: f32, + + // Enable/disable the joint motor + enableMotor: bool, + + // The maximum motor force, usually in newtons + maxMotorForce: f32, + + // The desired motor speed, usually in meters per second + motorSpeed: f32, + + // Set this flag to true if the attached bodies should collide + collideConnected: bool, + + // User data pointer + userData: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// A motor joint is used to control the relative motion between two bodies +// +// A typical usage is to control the movement of a dynamic body with respect to the ground. +// @ingroup motor_joint +MotorJointDef :: struct { + // The first attached body + bodyIdA: BodyId, + + // The second attached body + bodyIdB: BodyId, + + // Position of bodyB minus the position of bodyA, in bodyA's frame + linearOffset: Vec2, + + // The bodyB angle minus bodyA angle in radians + angularOffset: f32, + + // The maximum motor force in newtons + maxForce: f32, + + // The maximum motor torque in newton-meters + maxTorque: f32, + + // Position correction factor in the range [0,1] + correctionFactor: f32, + + // Set this flag to true if the attached bodies should collide + collideConnected: bool, + + // User data pointer + userData: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// A mouse joint is used to make a point on a body track a specified world point. +// +// This a soft constraint and allows the constraint to stretch without +// applying huge forces. This also applies rotation constraint heuristic to improve control. +// @ingroup mouse_joint +MouseJointDef :: struct { + // The first attached body. + bodyIdA: BodyId, + + // The second attached body. + bodyIdB: BodyId, + + // The initial target point in world space + target: Vec2, + + // Stiffness in hertz + hertz: f32, + + // Damping ratio, non-dimensional + dampingRatio: f32, + + // Maximum force, typically in newtons + maxForce: f32, + + // Set this flag to true if the attached bodies should collide. + collideConnected: bool, + + // User data pointer + userData: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// Prismatic joint definition +// +// This requires defining a line of motion using an axis and an anchor point. +// The definition uses local anchor points and a local axis so that the initial +// configuration can violate the constraint slightly. The joint translation is zero +// when the local anchor points coincide in world space. +// @ingroup prismatic_joint +PrismaticJointDef :: struct { + // The first attached body + bodyIdA: BodyId, + + // The second attached body + bodyIdB: BodyId, + + // The local anchor point relative to bodyA's origin + localAnchorA: Vec2, + + // The local anchor point relative to bodyB's origin + localAnchorB: Vec2, + + // The local translation unit axis in bodyA + localAxisA: Vec2, + + // The constrained angle between the bodies: bodyB_angle - bodyA_angle + referenceAngle: f32, + + // Enable a linear spring along the prismatic joint axis + enableSpring: bool, + + // The spring stiffness Hertz, cycles per second + hertz: f32, + + // The spring damping ratio, non-dimensional + dampingRatio: f32, + + // Enable/disable the joint limit + enableLimit: bool, + + // The lower translation limit + lowerTranslation: f32, + + // The upper translation limit + upperTranslation: f32, + + // Enable/disable the joint motor + enableMotor: bool, + + // The maximum motor force, typically in newtons + maxMotorForce: f32, + + // The desired motor speed, typically in meters per second + motorSpeed: f32, + + // Set this flag to true if the attached bodies should collide + collideConnected: bool, + + // User data pointer + userData: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// Revolute joint definition +// +// This requires defining an anchor point where the bodies are joined. +// The definition uses local anchor points so that the +// initial configuration can violate the constraint slightly. You also need to +// specify the initial relative angle for joint limits. This helps when saving +// and loading a game. +// The local anchor points are measured from the body's origin +// rather than the center of mass because: +// 1. you might not know where the center of mass will be +// 2. if you add/remove shapes from a body and recompute the mass, the joints will be broken +// @ingroup revolute_joint +RevoluteJointDef :: struct { + // The first attached body + bodyIdA: BodyId, + + // The second attached body + bodyIdB: BodyId, + + // The local anchor point relative to bodyA's origin + localAnchorA: Vec2, + + // The local anchor point relative to bodyB's origin + localAnchorB: Vec2, + + // The bodyB angle minus bodyA angle in the reference state (radians). + // This defines the zero angle for the joint limit. + referenceAngle: f32, + + // Enable a rotational spring on the revolute hinge axis + enableSpring: bool, + + // The spring stiffness Hertz, cycles per second + hertz: f32, + + // The spring damping ratio, non-dimensional + dampingRatio: f32, + + // A flag to enable joint limits + enableLimit: bool, + + // The lower angle for the joint limit in radians + lowerAngle: f32, + + // The upper angle for the joint limit in radians + upperAngle: f32, + + // A flag to enable the joint motor + enableMotor: bool, + + // The maximum motor torque, typically in newton-meters + maxMotorTorque: f32, + + // The desired motor speed in radians per second + motorSpeed: f32, + + // Scale the debug draw + drawSize: f32, + + // Set this flag to true if the attached bodies should collide + collideConnected: bool, + + // User data pointer + userData: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// Weld joint definition +// +// A weld joint connect to bodies together rigidly. This constraint provides springs to mimic +// soft-body simulation. +// @note The approximate solver in Box2D cannot hold many bodies together rigidly +// @ingroup weld_joint +WeldJointDef :: struct { + // The first attached body + bodyIdA: BodyId, + + // The second attached body + bodyIdB: BodyId, + + // The local anchor point relative to bodyA's origin + localAnchorA: Vec2, + + // The local anchor point relative to bodyB's origin + localAnchorB: Vec2, + + // The bodyB angle minus bodyA angle in the reference state (radians) + referenceAngle: f32, + + // Linear stiffness expressed as Hertz (cycles per second). Use zero for maximum stiffness. + linearHertz: f32, + + // Angular stiffness as Hertz (cycles per second). Use zero for maximum stiffness. + angularHertz: f32, + + // Linear damping ratio, non-dimensional. Use 1 for critical damping. + linearDampingRatio: f32, + + // Linear damping ratio, non-dimensional. Use 1 for critical damping. + angularDampingRatio: f32, + + // Set this flag to true if the attached bodies should collide + collideConnected: bool, + + // User data pointer + userData: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +// Wheel joint definition +// +// This requires defining a line of motion using an axis and an anchor point. +// The definition uses local anchor points and a local axis so that the initial +// configuration can violate the constraint slightly. The joint translation is zero +// when the local anchor points coincide in world space. +// @ingroup wheel_joint +WheelJointDef :: struct { + // The first attached body + bodyIdA: BodyId, + + // The second attached body + bodyIdB: BodyId, + + // The local anchor point relative to bodyA's origin + localAnchorA: Vec2, + + // The local anchor point relative to bodyB's origin + localAnchorB: Vec2, + + // The local translation unit axis in bodyA + localAxisA: Vec2, + + // Enable a linear spring along the local axis + enableSpring: bool, + + // Spring stiffness in Hertz + hertz: f32, + + // Spring damping ratio, non-dimensional + dampingRatio: f32, + + // Enable/disable the joint linear limit + enableLimit: bool, + + // The lower translation limit + lowerTranslation: f32, + + // The upper translation limit + upperTranslation: f32, + + // Enable/disable the joint rotational motor + enableMotor: bool, + + // The maximum motor torque, typically in newton-meters + maxMotorTorque: f32, + + // The desired motor speed in radians per second + motorSpeed: f32, + + // Set this flag to true if the attached bodies should collide + collideConnected: bool, + + // User data pointer + userData: rawptr, + + // Used internally to detect a valid definition. DO NOT SET. + internalValue: i32, +} + + +/** + * @defgroup events Events + * World event types. + * + * Events are used to collect events that occur during the world time step. These events + * are then available to query after the time step is complete. This is preferable to callbacks + * because Box2D uses multithreaded simulation. + * + * Also when events occur in the simulation step it may be problematic to modify the world, which is + * often what applications want to do when events occur. + * + * With event arrays, you can scan the events in a loop and modify the world. However, you need to be careful + * that some event data may become invalid. There are several samples that show how to do this safely. + * + * @{ + */ + +// A begin touch event is generated when a shape starts to overlap a sensor shape. +SensorBeginTouchEvent :: struct { + // The id of the sensor shape + sensorShapeId: ShapeId, + + // The id of the dynamic shape that began touching the sensor shape + visitorShapeId: ShapeId, +} + +// An end touch event is generated when a shape stops overlapping a sensor shape. +SensorEndTouchEvent :: struct { + // The id of the sensor shape + sensorShapeId: ShapeId, + + // The id of the dynamic shape that stopped touching the sensor shape + visitorShapeId: ShapeId, +} + +// Sensor events are buffered in the Box2D world and are available +// as begin/end overlap event arrays after the time step is complete. +// Note: these may become invalid if bodies and/or shapes are destroyed +SensorEvents :: struct { + // Array of sensor begin touch events + beginEvents: [^]SensorBeginTouchEvent `fmt:"v,beginCount"`, + + // Array of sensor end touch events + endEvents: [^]SensorEndTouchEvent `fmt:"v,endCount"`, + + // The number of begin touch events + beginCount: i32, + + // The number of end touch events + endCount: i32, +} + +// A begin touch event is generated when two shapes begin touching. +ContactBeginTouchEvent :: struct { + // Id of the first shape + shapeIdA: ShapeId, + + // Id of the second shape + shapeIdB: ShapeId, +} + +// An end touch event is generated when two shapes stop touching. +ContactEndTouchEvent :: struct { + // Id of the first shape + shapeIdA: ShapeId, + + // Id of the second shape + shapeIdB: ShapeId, +} + +// A hit touch event is generated when two shapes collide with a speed faster than the hit speed threshold. +ContactHitEvent :: struct { + // Id of the first shape + shapeIdA: ShapeId, + + // Id of the second shape + shapeIdB: ShapeId, + + // Point where the shapes hit + point: Vec2, + + // Normal vector pointing from shape A to shape B + normal: Vec2, + + // The speed the shapes are approaching. Always positive. Typically in meters per second. + approachSpeed: f32, +} + +// Contact events are buffered in the Box2D world and are available +// as event arrays after the time step is complete. +// Note: these may become invalid if bodies and/or shapes are destroyed +ContactEvents :: struct { + // Array of begin touch events + beginEvents: [^]ContactBeginTouchEvent `fmt:"v,beginCount"`, + + // Array of end touch events + endEvents: [^]ContactEndTouchEvent `fmt:"v,endCount"`, + + // Array of hit events + hitEvents: [^]ContactHitEvent `fmt:"v,hitCount"`, + + // Number of begin touch events + beginCount: i32, + + // Number of end touch events + endCount: i32, + + // Number of hit events + hitCount: i32, +} + +// Body move events triggered when a body moves. +// Triggered when a body moves due to simulation. Not reported for bodies moved by the user. +// This also has a flag to indicate that the body went to sleep so the application can also +// sleep that actor/entity/object associated with the body. +// On the other hand if the flag does not indicate the body went to sleep then the application +// can treat the actor/entity/object associated with the body as awake. +// This is an efficient way for an application to update game object transforms rather than +// calling functions such as b2Body_GetTransform() because this data is delivered as a contiguous array +// and it is only populated with bodies that have moved. +// @note If sleeping is disabled all dynamic and kinematic bodies will trigger move events. +BodyMoveEvent :: struct { + transform: Transform, + bodyId: BodyId, + userData: rawptr, + fellAsleep: bool, +} + +// Body events are buffered in the Box2D world and are available +// as event arrays after the time step is complete. +// Note: this date becomes invalid if bodies are destroyed +BodyEvents :: struct { + // Array of move events + moveEvents: [^]BodyMoveEvent `fmt:"v,moveCount"`, + + // Number of move events + moveCount: i32, + +} + +// The contact data for two shapes. By convention the manifold normal points +// from shape A to shape B. +// @see b2Shape_GetContactData() and b2Body_GetContactData() +ContactData :: struct { + shapeIdA: ShapeId, + shapeIdB: ShapeId, + manifold: Manifold, +} + +/**@}*/ + +// Prototype for a contact filter callback. +// This is called when a contact pair is considered for collision. This allows you to +// perform custom logic to prevent collision between shapes. This is only called if +// one of the two shapes has custom filtering enabled. @see b2ShapeDef. +// Notes: +// - this function must be thread-safe +// - this is only called if one of the two shapes has enabled custom filtering +// - this is called only for awake dynamic bodies +// Return false if you want to disable the collision +// @warning Do not attempt to modify the world inside this callback +// @ingroup world +CustomFilterFcn :: #type proc "c" (shapeIdA, shapeIdB: ShapeId, ctx: rawptr) -> bool + +// Prototype for a pre-solve callback. +// This is called after a contact is updated. This allows you to inspect a +// contact before it goes to the solver. If you are careful, you can modify the +// contact manifold (e.g. modify the normal). +// Notes: +// - this function must be thread-safe +// - this is only called if the shape has enabled pre-solve events +// - this is called only for awake dynamic bodies +// - this is not called for sensors +// - the supplied manifold has impulse values from the previous step +// Return false if you want to disable the contact this step +// @warning Do not attempt to modify the world inside this callback +// @ingroup world +PreSolveFcn :: #type proc "c" (shapeIdA, shapeIdB: ShapeId, manifold: ^Manifold, ctx: rawptr) -> bool + +// Prototype callback for overlap queries. +// Called for each shape found in the query. +// @see b2World_QueryAABB +// @return false to terminate the query. +// @ingroup world +OverlapResultFcn :: #type proc "c" (shapeId: ShapeId, ctx: rawptr) -> bool + +// Prototype callback for ray casts. +// Called for each shape found in the query. You control how the ray cast +// proceeds by returning a f32: +// return -1: ignore this shape and continue +// return 0: terminate the ray cast +// return fraction: clip the ray to this point +// return 1: don't clip the ray and continue +// @param shapeId the shape hit by the ray +// @param point the point of initial intersection +// @param normal the normal vector at the point of intersection +// @param fraction the fraction along the ray at the point of intersection +// @param context the user context +// @return -1 to filter, 0 to terminate, fraction to clip the ray for closest hit, 1 to continue +// @see b2World_CastRay +// @ingroup world +CastResultFcn :: #type proc "c" (shapeId: ShapeId, point: Vec2, normal: Vec2, fraction: f32, ctx: rawptr) -> f32 + +// These colors are used for debug draw. +// See https://www.rapidtables.com/web/color/index.html +HexColor :: enum c.int { + AliceBlue = 0xf0f8ff, + AntiqueWhite = 0xfaebd7, + Aqua = 0x00ffff, + Aquamarine = 0x7fffd4, + Azure = 0xf0ffff, + Beige = 0xf5f5dc, + Bisque = 0xffe4c4, + Black = 0x000000, + BlanchedAlmond = 0xffebcd, + Blue = 0x0000ff, + BlueViolet = 0x8a2be2, + Brown = 0xa52a2a, + Burlywood = 0xdeb887, + CadetBlue = 0x5f9ea0, + Chartreuse = 0x7fff00, + Chocolate = 0xd2691e, + Coral = 0xff7f50, + CornflowerBlue = 0x6495ed, + Cornsilk = 0xfff8dc, + Crimson = 0xdc143c, + Cyan = 0x00ffff, + DarkBlue = 0x00008b, + DarkCyan = 0x008b8b, + DarkGoldenrod = 0xb8860b, + DarkGray = 0xa9a9a9, + DarkGreen = 0x006400, + DarkKhaki = 0xbdb76b, + DarkMagenta = 0x8b008b, + DarkOliveGreen = 0x556b2f, + DarkOrange = 0xff8c00, + DarkOrchid = 0x9932cc, + DarkRed = 0x8b0000, + DarkSalmon = 0xe9967a, + DarkSeaGreen = 0x8fbc8f, + DarkSlateBlue = 0x483d8b, + DarkSlateGray = 0x2f4f4f, + DarkTurquoise = 0x00ced1, + DarkViolet = 0x9400d3, + DeepPink = 0xff1493, + DeepSkyBlue = 0x00bfff, + DimGray = 0x696969, + DodgerBlue = 0x1e90ff, + Firebrick = 0xb22222, + FloralWhite = 0xfffaf0, + ForestGreen = 0x228b22, + Fuchsia = 0xff00ff, + Gainsboro = 0xdcdcdc, + GhostWhite = 0xf8f8ff, + Gold = 0xffd700, + Goldenrod = 0xdaa520, + Gray = 0xbebebe, + Gray1 = 0x1a1a1a, + Gray2 = 0x333333, + Gray3 = 0x4d4d4d, + Gray4 = 0x666666, + Gray5 = 0x7f7f7f, + Gray6 = 0x999999, + Gray7 = 0xb3b3b3, + Gray8 = 0xcccccc, + Gray9 = 0xe5e5e5, + Green = 0x00ff00, + GreenYellow = 0xadff2f, + Honeydew = 0xf0fff0, + HotPink = 0xff69b4, + IndianRed = 0xcd5c5c, + Indigo = 0x4b0082, + Ivory = 0xfffff0, + Khaki = 0xf0e68c, + Lavender = 0xe6e6fa, + LavenderBlush = 0xfff0f5, + LawnGreen = 0x7cfc00, + LemonChiffon = 0xfffacd, + LightBlue = 0xadd8e6, + LightCoral = 0xf08080, + LightCyan = 0xe0ffff, + LightGoldenrod = 0xeedd82, + LightGoldenrodYellow = 0xfafad2, + LightGray = 0xd3d3d3, + LightGreen = 0x90ee90, + LightPink = 0xffb6c1, + LightSalmon = 0xffa07a, + LightSeaGreen = 0x20b2aa, + LightSkyBlue = 0x87cefa, + LightSlateBlue = 0x8470ff, + LightSlateGray = 0x778899, + LightSteelBlue = 0xb0c4de, + LightYellow = 0xffffe0, + Lime = 0x00ff00, + LimeGreen = 0x32cd32, + Linen = 0xfaf0e6, + Magenta = 0xff00ff, + Maroon = 0xb03060, + MediumAquamarine = 0x66cdaa, + MediumBlue = 0x0000cd, + MediumOrchid = 0xba55d3, + MediumPurple = 0x9370db, + MediumSeaGreen = 0x3cb371, + MediumSlateBlue = 0x7b68ee, + MediumSpringGreen = 0x00fa9a, + MediumTurquoise = 0x48d1cc, + MediumVioletRed = 0xc71585, + MidnightBlue = 0x191970, + MintCream = 0xf5fffa, + MistyRose = 0xffe4e1, + Moccasin = 0xffe4b5, + NavajoWhite = 0xffdead, + Navy = 0x000080, + NavyBlue = 0x000080, + OldLace = 0xfdf5e6, + Olive = 0x808000, + OliveDrab = 0x6b8e23, + Orange = 0xffa500, + OrangeRed = 0xff4500, + Orchid = 0xda70d6, + PaleGoldenrod = 0xeee8aa, + PaleGreen = 0x98fb98, + PaleTurquoise = 0xafeeee, + PaleVioletRed = 0xdb7093, + PapayaWhip = 0xffefd5, + PeachPuff = 0xffdab9, + Peru = 0xcd853f, + Pink = 0xffc0cb, + Plum = 0xdda0dd, + PowderBlue = 0xb0e0e6, + Purple = 0xa020f0, + RebeccaPurple = 0x663399, + Red = 0xff0000, + RosyBrown = 0xbc8f8f, + RoyalBlue = 0x4169e1, + SaddleBrown = 0x8b4513, + Salmon = 0xfa8072, + SandyBrown = 0xf4a460, + SeaGreen = 0x2e8b57, + Seashell = 0xfff5ee, + Sienna = 0xa0522d, + Silver = 0xc0c0c0, + SkyBlue = 0x87ceeb, + SlateBlue = 0x6a5acd, + SlateGray = 0x708090, + Snow = 0xfffafa, + SpringGreen = 0x00ff7f, + SteelBlue = 0x4682b4, + Tan = 0xd2b48c, + Teal = 0x008080, + Thistle = 0xd8bfd8, + Tomato = 0xff6347, + Turquoise = 0x40e0d0, + Violet = 0xee82ee, + VioletRed = 0xd02090, + Wheat = 0xf5deb3, + White = 0xffffff, + WhiteSmoke = 0xf5f5f5, + Yellow = 0xffff00, + YellowGreen = 0x9acd32, + Box2DRed = 0xdc3132, + Box2DBlue = 0x30aebf, + Box2DGreen = 0x8cc924, + Box2DYellow = 0xffee8c, +} + +// This struct holds callbacks you can implement to draw a Box2D world. +// @ingroup world +DebugDraw :: struct { + // Draw a closed polygon provided in CCW order. + DrawPolygon: proc "c" (vertices: [^]Vec2, vertexCount: c.int, color: HexColor, ctx: rawptr), + + // Draw a solid closed polygon provided in CCW order. + DrawSolidPolygon: proc "c" (transform: Transform, vertices: [^]Vec2, vertexCount: c.int, radius: f32, colr: HexColor, ctx: rawptr ), + + // Draw a circle. + DrawCircle: proc "c" (center: Vec2, radius: f32, color: HexColor, ctx: rawptr), + + // Draw a solid circle. + DrawSolidCircle: proc "c" (transform: Transform, radius: f32, color: HexColor, ctx: rawptr), + + // Draw a capsule. + DrawCapsule: proc "c" (p1, p2: Vec2, radius: f32, color: HexColor, ctx: rawptr), + + // Draw a solid capsule. + DrawSolidCapsule: proc "c" (p1, p2: Vec2, radius: f32, color: HexColor, ctx: rawptr), + + // Draw a line segment. + DrawSegment: proc "c" (p1, p2: Vec2, color: HexColor, ctx: rawptr), + + // Draw a transform. Choose your own length scale. + DrawTransform: proc "c" (transform: Transform, ctx: rawptr), + + // Draw a point. + DrawPoint: proc "c" (p: Vec2, size: f32, color: HexColor, ctx: rawptr), + + // Draw a string. + DrawString: proc "c" (p: Vec2, s: cstring, ctx: rawptr), + + // Bounds to use if restricting drawing to a rectangular region + drawingBounds: AABB, + + // Option to restrict drawing to a rectangular region. May suffer from unstable depth sorting. + useDrawingBounds: bool, + + // Option to draw shapes + drawShapes: bool, + + // Option to draw joints + drawJoints: bool, + + // Option to draw additional information for joints + drawJointExtras: bool, + + // Option to draw the bounding boxes for shapes + drawAABBs: bool, + + // Option to draw the mass and center of mass of dynamic bodies + drawMass: bool, + + // Option to draw contact points + drawContacts: bool, + + // Option to visualize the graph coloring used for contacts and joints + drawGraphColors: bool, + + // Option to draw contact normals + drawContactNormals: bool, + + // Option to draw contact normal impulses + drawContactImpulses: bool, + + // Option to draw contact friction impulses + drawFrictionImpulses: bool, + + // User context that is passed as an argument to drawing callback functions + userContext: rawptr, +} \ No newline at end of file diff --git a/vendor/cgltf/cgltf.odin b/vendor/cgltf/cgltf.odin index a5d474a7b..f4518360d 100644 --- a/vendor/cgltf/cgltf.odin +++ b/vendor/cgltf/cgltf.odin @@ -168,7 +168,7 @@ buffer :: struct { data_free_method: data_free_method, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } meshopt_compression_mode :: enum c.int { @@ -207,7 +207,7 @@ buffer_view :: struct { meshopt_compression: meshopt_compression, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } accessor_sparse :: struct { @@ -221,11 +221,11 @@ accessor_sparse :: struct { indices_extras: extras_t, values_extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, indices_extensions_count: uint, - indices_extensions: [^]extension, + indices_extensions: [^]extension `fmt:"v,indices_extensions_count"`, values_extensions_count: uint, - values_extensions: [^]extension, + values_extensions: [^]extension `fmt:"v,values_extensions_count"`, } accessor :: struct { @@ -245,7 +245,7 @@ accessor :: struct { sparse: accessor_sparse, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } attribute :: struct { @@ -262,7 +262,7 @@ image :: struct { mime_type: cstring, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } sampler :: struct { @@ -273,7 +273,7 @@ sampler :: struct { wrap_t: c.int, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } texture :: struct { @@ -284,7 +284,7 @@ texture :: struct { basisu_image: ^image, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } texture_transform :: struct { @@ -303,7 +303,7 @@ texture_view :: struct { transform: texture_transform, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } pbr_metallic_roughness :: struct { @@ -408,7 +408,7 @@ material :: struct { unlit: b32, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } material_mapping :: struct { @@ -442,7 +442,7 @@ primitive :: struct { draco_mesh_compression: draco_mesh_compression, mappings: []material_mapping, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } mesh :: struct { @@ -452,7 +452,7 @@ mesh :: struct { target_names: []cstring, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } skin :: struct { @@ -462,7 +462,7 @@ skin :: struct { inverse_bind_matrices: ^accessor, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } camera_perspective :: struct { @@ -492,7 +492,7 @@ camera :: struct { }, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } light :: struct { @@ -527,7 +527,7 @@ node :: struct { has_mesh_gpu_instancing: b32, mesh_gpu_instancing: mesh_gpu_instancing, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } scene :: struct { @@ -535,7 +535,7 @@ scene :: struct { nodes: []^node, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } animation_sampler :: struct { @@ -544,7 +544,7 @@ animation_sampler :: struct { interpolation: interpolation_type, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } animation_channel :: struct { @@ -553,7 +553,7 @@ animation_channel :: struct { target_path: animation_path_type, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } animation :: struct { @@ -562,7 +562,7 @@ animation :: struct { channels: []animation_channel, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } material_variant :: struct { @@ -577,7 +577,7 @@ asset :: struct { min_version: cstring, extras: extras_t, extensions_count: uint, - extensions: [^]extension, + extensions: [^]extension `fmt:"v,extensions_count"`, } data :: struct { @@ -609,7 +609,7 @@ data :: struct { extras: extras_t, data_extensions_count: uint, - data_extensions: [^]extension, + data_extensions: [^]extension `fmt:"v,extensions_count"`, extensions_used: []cstring, extensions_required: []cstring, diff --git a/vendor/commonmark/cmark.odin b/vendor/commonmark/cmark.odin index 50544b9bd..2fdf1387c 100644 --- a/vendor/commonmark/cmark.odin +++ b/vendor/commonmark/cmark.odin @@ -338,7 +338,7 @@ foreign lib { node_set_list_tight :: proc(node: ^Node, tight: b32) -> (success: b32) --- // Returns the info string from a fenced code block. - get_fence_info :: proc(node: ^Node) -> (fence_info: cstring) --- + node_get_fence_info :: proc(node: ^Node) -> (fence_info: cstring) --- // Sets the info string in a fenced code block, returning `true` on success and `false` on failure. node_set_fence_info :: proc(node: ^Node, fence_info: cstring) -> (success: b32) --- diff --git a/vendor/directx/d3d12/d3d12.odin b/vendor/directx/d3d12/d3d12.odin index e33845b73..4e51d5203 100644 --- a/vendor/directx/d3d12/d3d12.odin +++ b/vendor/directx/d3d12/d3d12.odin @@ -3459,10 +3459,10 @@ DRED_DEVICE_STATE :: enum i32 { } DRED_PAGE_FAULT_OUTPUT2 :: struct { - PageFaultVA: GPU_VIRTUAL_ADDRESS, - pHeadExistingAllocationNode: ^DRED_ALLOCATION_NODE1, - pHeadRecentFreedAllocationNode: ^DRED_ALLOCATION_NODE1, - PageFaultFlags: DRED_PAGE_FAULT_FLAGS, + PageFaultVA: GPU_VIRTUAL_ADDRESS, + pHeadExistingAllocationNode: ^DRED_ALLOCATION_NODE1, + pHeadRecentFreedAllocationNode: ^DRED_ALLOCATION_NODE1, + PageFaultFlags: DRED_PAGE_FAULT_FLAGS, } DEVICE_REMOVED_EXTENDED_DATA1 :: struct { diff --git a/vendor/directx/dxc/dxcapi.odin b/vendor/directx/dxc/dxcapi.odin index 79d305ea0..23dbc8f30 100644 --- a/vendor/directx/dxc/dxcapi.odin +++ b/vendor/directx/dxc/dxcapi.odin @@ -476,8 +476,8 @@ IVersionInfo3_VTable :: struct { } ArgPair :: struct { - pName: wstring, - pValue: wstring, + pName: wstring, + pValue: wstring, } IPdbUtils_UUID_STRING :: "E6C9647E-9D6A-4C3B-B94C-524B5A6C343D" diff --git a/vendor/egl/egl.odin b/vendor/egl/egl.odin index 82181b1c5..e455a3d42 100644 --- a/vendor/egl/egl.odin +++ b/vendor/egl/egl.odin @@ -8,25 +8,38 @@ Surface :: distinct rawptr Config :: distinct rawptr Context :: distinct rawptr +Boolean :: b32 + +FALSE :: false +TRUE :: true + NO_DISPLAY :: Display(uintptr(0)) NO_CONTEXT :: Context(uintptr(0)) NO_SURFACE :: Surface(uintptr(0)) +DEFAULT_DISPLAY :: NativeDisplayType(uintptr(0)) + CONTEXT_OPENGL_CORE_PROFILE_BIT :: 0x00000001 WINDOW_BIT :: 0x0004 OPENGL_BIT :: 0x0008 +OPENGL_ES2_BIT :: 0x0004 +OPENGL_ES3_BIT :: 0x00000040 +ALPHA_SIZE :: 0x3021 BLUE_SIZE :: 0x3022 GREEN_SIZE :: 0x3023 RED_SIZE :: 0x3024 DEPTH_SIZE :: 0x3025 STENCIL_SIZE :: 0x3026 +NATIVE_VISUAL_ID :: 0x302E SURFACE_TYPE :: 0x3033 NONE :: 0x3038 COLOR_BUFFER_TYPE :: 0x303F RENDERABLE_TYPE :: 0x3040 CONFORMANT :: 0x3042 +HEIGHT :: 0x3056 +WIDTH :: 0x3057 BACK_BUFFER :: 0x3084 RENDER_BUFFER :: 0x3086 @@ -35,6 +48,7 @@ GL_COLORSPACE_LINEAR :: 0x308A RGB_BUFFER :: 0x308E GL_COLORSPACE :: 0x309D +CONTEXT_CLIENT_VERSION :: 0x3098 CONTEXT_MAJOR_VERSION :: 0x3098 CONTEXT_MINOR_VERSION :: 0x30FB CONTEXT_OPENGL_PROFILE_MASK :: 0x30FD @@ -45,15 +59,20 @@ foreign import egl "system:EGL" @(default_calling_convention="c", link_prefix="egl") foreign egl { GetDisplay :: proc(display: NativeDisplayType) -> Display --- - Initialize :: proc(display: Display, major: ^i32, minor: ^i32) -> i32 --- - BindAPI :: proc(api: u32) -> i32 --- - ChooseConfig :: proc(display: Display, attrib_list: ^i32, configs: ^Config, config_size: i32, num_config: ^i32) -> i32 --- + Initialize :: proc(display: Display, major: ^i32, minor: ^i32) -> Boolean --- + BindAPI :: proc(api: u32) -> Boolean --- + ChooseConfig :: proc(display: Display, attrib_list: ^i32, configs: ^Config, config_size: i32, num_config: ^i32) -> Boolean --- CreateWindowSurface :: proc(display: Display, config: Config, native_window: NativeWindowType, attrib_list: ^i32) -> Surface --- CreateContext :: proc(display: Display, config: Config, share_context: Context, attrib_list: ^i32) -> Context --- - MakeCurrent :: proc(display: Display, draw: Surface, read: Surface, ctx: Context) -> i32 --- - SwapInterval :: proc(display: Display, interval: i32) -> i32 --- - SwapBuffers :: proc(display: Display, surface: Surface) -> i32 --- + MakeCurrent :: proc(display: Display, draw: Surface, read: Surface, ctx: Context) -> Boolean --- + QuerySurface :: proc(display: Display, surface: Surface, attribute: i32, value: ^i32) -> Boolean --- + SwapInterval :: proc(display: Display, interval: i32) -> Boolean --- + SwapBuffers :: proc(display: Display, surface: Surface) -> Boolean --- GetProcAddress :: proc(name: cstring) -> rawptr --- + GetConfigAttrib :: proc(display: Display, config: Config, attribute: i32, value: ^i32) -> Boolean --- + DestroyContext :: proc(display: Display, ctx: Context) -> Boolean --- + DestroySurface :: proc(display: Display, surface: Surface) -> Boolean --- + Terminate :: proc(display: Display) -> Boolean --- } gl_set_proc_address :: proc(p: rawptr, name: cstring) { diff --git a/vendor/ggpo/ggpo.odin b/vendor/ggpo/ggpo.odin index b38f4fb65..59321326a 100644 --- a/vendor/ggpo/ggpo.odin +++ b/vendor/ggpo/ggpo.odin @@ -1,3 +1,15 @@ +/* +Created in 2009, the GGPO networking SDK pioneered the use of rollback networking in peer-to-peer games. +It's designed specifically to hide network latency in fast paced, twitch style games which require very +precise inputs and frame perfect execution. + +Traditional techniques account for network transmission time by adding delay to a players input, resulting +in a sluggish, laggy game-feel. Rollback networking uses input prediction and speculative execution to +send player inputs to the game immediately, providing the illusion of a zero-latency network. Using rollback, +the same timings, reactions visual and audio queues, and muscle memory your players build up playing offline +translate directly online. The GGPO networking SDK is designed to make incorporating rollback networking +into new and existing games as easy as possible. +*/ package vendor_ggpo foreign import lib "GGPO.lib" @@ -19,31 +31,27 @@ PlayerType :: enum c.int { SPECTATOR, } -/* - * The Player structure used to describe players in add_player - * - * size: Should be set to the size_of(Player) - * - * type: One of the PlayerType values describing how inputs should be handled - * Local players must have their inputs updated every frame via - * add_local_inputs. Remote players values will come over the - * network. - * - * player_num: The player number. Should be between 1 and the number of players - * In the game (e.g. in a 2 player game, either 1 or 2). - * - * If type == PLAYERTYPE_REMOTE: - * - * remote.ip_address: The ip address of the ggpo session which will host this - * player. - * - * remote.port: The port where udp packets should be sent to reach this player. - * All the local inputs for this session will be sent to this player at - * ip_address:port. - * - */ - +// The Player structure used to describe players in add_player +// +// size: Should be set to the size_of(Player) +// +// type: One of the PlayerType values describing how inputs should be handled +// Local players must have their inputs updated every frame via +// add_local_inputs. Remote players values will come over the +// network. +// +// player_num: The player number. Should be between 1 and the number of players +// In the game (e.g. in a 2 player game, either 1 or 2). +// +// If type == PLAYERTYPE_REMOTE: +// +// remote.ip_address: The ip address of the ggpo session which will host this +// player. +// +// remote.port: The port where udp packets should be sent to reach this player. +// All the local inputs for this session will be sent to this player at +// ip_address:port. Player :: struct { size: c.int, type: PlayerType, @@ -80,32 +88,29 @@ ErrorCode :: enum c.int { INVALID_HANDLE :: PlayerHandle(-1) -/* - * The EventCode enumeration describes what type of event just happened. - * - * CONNECTED_TO_PEER - Handshake with the game running on the - * other side of the network has been completed. - * - * SYNCHRONIZING_WITH_PEER - Beginning the synchronization - * process with the client on the other end of the networking. The count - * and total fields in the u.synchronizing struct of the Event - * object indicate progress. - * - * SYNCHRONIZED_WITH_PEER - The synchronziation with this - * peer has finished. - * - * RUNNING - All the clients have synchronized. You may begin - * sending inputs with synchronize_inputs. - * - * DISCONNECTED_FROM_PEER - The network connection on - * the other end of the network has closed. - * - * TIMESYNC - The time synchronziation code has determined - * that this client is too far ahead of the other one and should slow - * down to ensure fairness. The u.timesync.frames_ahead parameter in - * the Event object indicates how many frames the client is. - * - */ +// The EventCode enumeration describes what type of event just happened. +// +// CONNECTED_TO_PEER - Handshake with the game running on the +// other side of the network has been completed. +// +// SYNCHRONIZING_WITH_PEER - Beginning the synchronization +// process with the client on the other end of the networking. The count +// and total fields in the u.synchronizing struct of the Event +// object indicate progress. +// +// SYNCHRONIZED_WITH_PEER - The synchronziation with this +// peer has finished. +// +// RUNNING - All the clients have synchronized. You may begin +// sending inputs with synchronize_inputs. +// +// DISCONNECTED_FROM_PEER - The network connection on +// the other end of the network has closed. +// +// TIMESYNC - The time synchronziation code has determined +// that this client is too far ahead of the other one and should slow +// down to ensure fairness. The u.timesync.frames_ahead parameter in +// the Event object indicates how many frames the client is. EventCode :: enum c.int { CONNECTED_TO_PEER = 1000, SYNCHRONIZING_WITH_PEER = 1001, @@ -117,13 +122,10 @@ EventCode :: enum c.int { CONNECTION_RESUMED = 1007, } -/* - * The Event structure contains an asynchronous event notification sent - * by the on_event callback. See EventCode, above, for a detailed - * explanation of each event. - */ - - Event :: struct { +// The Event structure contains an asynchronous event notification sent +// by the on_event callback. See EventCode, above, for a detailed +// explanation of each event. +Event :: struct { code: EventCode, using u: struct #raw_union { connected: struct { @@ -153,100 +155,83 @@ EventCode :: enum c.int { }, } -/* - * The SessionCallbacks structure contains the callback functions that - * your application must implement. GGPO.net will periodically call these - * functions during the game. All callback functions must be implemented. - */ +// +// The SessionCallbacks structure contains the callback functions that +// your application must implement. GGPO.net will periodically call these +// functions during the game. All callback functions must be implemented. +// SessionCallbacks :: struct { - /* - * begin_game callback - This callback has been deprecated. You must - * implement it, but should ignore the 'game' parameter. - */ + // begin_game callback - This callback has been deprecated. You must + // implement it, but should ignore the 'game' parameter. begin_game: proc "c" (game: cstring) -> bool, - /* - * save_game_state - The client should allocate a buffer, copy the - * entire contents of the current game state into it, and copy the - * length into the len parameter. Optionally, the client can compute - * a checksum of the data and store it in the checksum argument. - */ + // save_game_state - The client should allocate a buffer, copy the + // entire contents of the current game state into it, and copy the + // length into the len parameter. Optionally, the client can compute + // a checksum of the data and store it in the checksum argument. save_game_state: proc "c" (buffer: ^[^]byte, len: ^c.int, checksum: ^c.int, frame: c.int) -> bool, - /* - * load_game_state - GGPO.net will call this function at the beginning - * of a rollback. The buffer and len parameters contain a previously - * saved state returned from the save_game_state function. The client - * should make the current game state match the state contained in the - * buffer. - */ + // load_game_state - GGPO.net will call this function at the beginning + // of a rollback. The buffer and len parameters contain a previously + // saved state returned from the save_game_state function. The client + // should make the current game state match the state contained in the + // buffer. load_game_state: proc "c" (buffer: [^]byte, len: c.int) -> bool, - /* - * log_game_state - Used in diagnostic testing. The client should use - * the log function to write the contents of the specified save - * state in a human readible form. - */ + // log_game_state - Used in diagnostic testing. The client should use + // the log function to write the contents of the specified save + // state in a human readible form. log_game_state: proc "c" (filename: cstring, buffer: [^]byte, len: c.int) -> bool, - /* - * free_buffer - Frees a game state allocated in save_game_state. You - * should deallocate the memory contained in the buffer. - */ + // free_buffer - Frees a game state allocated in save_game_state. You + // should deallocate the memory contained in the buffer. free_buffer: proc "c" (buffer: rawptr), - /* - * advance_frame - Called during a rollback. You should advance your game - * state by exactly one frame. Before each frame, call synchronize_input - * to retrieve the inputs you should use for that frame. After each frame, - * you should call advance_frame to notify GGPO.net that you're - * finished. - * - * The flags parameter is reserved. It can safely be ignored at this time. - */ + // advance_frame - Called during a rollback. You should advance your game + // state by exactly one frame. Before each frame, call synchronize_input + // to retrieve the inputs you should use for that frame. After each frame, + // you should call advance_frame to notify GGPO.net that you're + // finished. + // + // The flags parameter is reserved. It can safely be ignored at this time. advance_frame: proc "c" (flags: c.int) -> bool, - /* - * on_event - Notification that something has happened. See the EventCode - * structure above for more information. - */ + // on_event - Notification that something has happened. See the EventCode + // structure above for more information. on_event: proc "c" (info: ^Event) -> bool, } -/* - * The NetworkStats function contains some statistics about the current - * session. - * - * network.send_queue_len - The length of the queue containing UDP packets - * which have not yet been acknowledged by the end client. The length of - * the send queue is a rough indication of the quality of the connection. - * The longer the send queue, the higher the round-trip time between the - * clients. The send queue will also be longer than usual during high - * packet loss situations. - * - * network.recv_queue_len - The number of inputs currently buffered by the - * GGPO.net network layer which have yet to be validated. The length of - * the prediction queue is roughly equal to the current frame number - * minus the frame number of the last packet in the remote queue. - * - * network.ping - The roundtrip packet transmission time as calcuated - * by GGPO.net. This will be roughly equal to the actual round trip - * packet transmission time + 2 the interval at which you call idle - * or advance_frame. - * - * network.kbps_sent - The estimated bandwidth used between the two - * clients, in kilobits per second. - * - * timesync.local_frames_behind - The number of frames GGPO.net calculates - * that the local client is behind the remote client at this instant in - * time. For example, if at this instant the current game client is running - * frame 1002 and the remote game client is running frame 1009, this value - * will mostly likely roughly equal 7. - * - * timesync.remote_frames_behind - The same as local_frames_behind, but - * calculated from the perspective of the remote player. - * - */ +// The NetworkStats function contains some statistics about the current +// session. +// +// network.send_queue_len - The length of the queue containing UDP packets +// which have not yet been acknowledged by the end client. The length of +// the send queue is a rough indication of the quality of the connection. +// The longer the send queue, the higher the round-trip time between the +// clients. The send queue will also be longer than usual during high +// packet loss situations. +// +// network.recv_queue_len - The number of inputs currently buffered by the +// GGPO.net network layer which have yet to be validated. The length of +// the prediction queue is roughly equal to the current frame number +// minus the frame number of the last packet in the remote queue. +// +// network.ping - The roundtrip packet transmission time as calcuated +// by GGPO.net. This will be roughly equal to the actual round trip +// packet transmission time + 2 the interval at which you call idle +// or advance_frame. +// +// network.kbps_sent - The estimated bandwidth used between the two +// clients, in kilobits per second. +// +// timesync.local_frames_behind - The number of frames GGPO.net calculates +// that the local client is behind the remote client at this instant in +// time. For example, if at this instant the current game client is running +// frame 1002 and the remote game client is running frame 1009, this value +// will mostly likely roughly equal 7. +// +// timesync.remote_frames_behind - The same as local_frames_behind, but +// calculated from the perspective of the remote player. NetworkStats :: struct { network: struct { send_queue_len: c.int, @@ -263,29 +248,27 @@ NetworkStats :: struct { @(default_calling_convention="c") @(link_prefix="ggpo_") foreign lib { - /* - * start_session -- - * - * Used to being a new GGPO.net session. The ggpo object returned by start_session - * uniquely identifies the state for this session and should be passed to all other - * functions. - * - * session - An out parameter to the new ggpo session object. - * - * cb - A SessionCallbacks structure which contains the callbacks you implement - * to help GGPO.net synchronize the two games. You must implement all functions in - * cb, even if they do nothing but 'return true'; - * - * game - The name of the game. This is used internally for GGPO for logging purposes only. - * - * num_players - The number of players which will be in this game. The number of players - * per session is fixed. If you need to change the number of players or any player - * disconnects, you must start a new session. - * - * input_size - The size of the game inputs which will be passsed to add_local_input. - * - * local_port - The port GGPO should bind to for UDP traffic. - */ + // start_session -- + // + // Used to being a new GGPO.net session. The ggpo object returned by start_session + // uniquely identifies the state for this session and should be passed to all other + // functions. + // + // session - An out parameter to the new ggpo session object. + // + // cb - A SessionCallbacks structure which contains the callbacks you implement + // to help GGPO.net synchronize the two games. You must implement all functions in + // cb, even if they do nothing but 'return true'; + // + // game - The name of the game. This is used internally for GGPO for logging purposes only. + // + // num_players - The number of players which will be in this game. The number of players + // per session is fixed. If you need to change the number of players or any player + // disconnects, you must start a new session. + // + // input_size - The size of the game inputs which will be passsed to add_local_input. + // + // local_port - The port GGPO should bind to for UDP traffic. start_session :: proc(session: ^^Session, cb: ^SessionCallbacks, game: cstring, @@ -294,17 +277,15 @@ foreign lib { localport: u16) -> ErrorCode --- - /* - * add_player -- - * - * Must be called for each player in the session (e.g. in a 3 player session, must - * be called 3 times). - * - * player - A Player struct used to describe the player. - * - * handle - An out parameter to a handle used to identify this player in the future. - * (e.g. in the on_event callbacks). - */ + // add_player -- + // + // Must be called for each player in the session (e.g. in a 3 player session, must + // be called 3 times). + // + // player - A Player struct used to describe the player. + // + // handle - An out parameter to a handle used to identify this player in the future. + // (e.g. in the on_event callbacks). add_player :: proc(session: ^Session, player: ^Player, handle: ^PlayerHandle) -> ErrorCode --- @@ -342,30 +323,28 @@ foreign lib { frames: c.int) -> ErrorCode --- - /* - * start_spectating -- - * - * Start a spectator session. - * - * cb - A SessionCallbacks structure which contains the callbacks you implement - * to help GGPO.net synchronize the two games. You must implement all functions in - * cb, even if they do nothing but 'return true'; - * - * game - The name of the game. This is used internally for GGPO for logging purposes only. - * - * num_players - The number of players which will be in this game. The number of players - * per session is fixed. If you need to change the number of players or any player - * disconnects, you must start a new session. - * - * input_size - The size of the game inputs which will be passsed to add_local_input. - * - * local_port - The port GGPO should bind to for UDP traffic. - * - * host_ip - The IP address of the host who will serve you the inputs for the game. Any - * player partcipating in the session can serve as a host. - * - * host_port - The port of the session on the host - */ + // start_spectating -- + // + // Start a spectator session. + // + // cb - A SessionCallbacks structure which contains the callbacks you implement + // to help GGPO.net synchronize the two games. You must implement all functions in + // cb, even if they do nothing but 'return true'; + // + // game - The name of the game. This is used internally for GGPO for logging purposes only. + // + // num_players - The number of players which will be in this game. The number of players + // per session is fixed. If you need to change the number of players or any player + // disconnects, you must start a new session. + // + // input_size - The size of the game inputs which will be passsed to add_local_input. + // + // local_port - The port GGPO should bind to for UDP traffic. + // + // host_ip - The IP address of the host who will serve you the inputs for the game. Any + // player partcipating in the session can serve as a host. + // + // host_port - The port of the session on the host start_spectating :: proc(session: ^^Session, cb: ^SessionCallbacks, game: cstring, @@ -375,152 +354,129 @@ foreign lib { host_ip: cstring, host_port: u16) -> ErrorCode --- - /* - * close_session -- - * Used to close a session. You must call close_session to - * free the resources allocated in start_session. - */ + // close_session -- + // Used to close a session. You must call close_session to + // free the resources allocated in start_session. close_session :: proc(session: ^Session) -> ErrorCode --- - /* - * set_frame_delay -- - * - * Change the amount of frames ggpo will delay local input. Must be called - * before the first call to synchronize_input. - */ + // set_frame_delay -- + // + // Change the amount of frames ggpo will delay local input. Must be called + // before the first call to synchronize_input. set_frame_delay :: proc(session: ^Session, player: PlayerHandle, frame_delay: c.int) -> ErrorCode --- - /* - * idle -- - * Should be called periodically by your application to give GGPO.net - * a chance to do some work. Most packet transmissions and rollbacks occur - * in idle. - * - * timeout - The amount of time GGPO.net is allowed to spend in this function, - * in milliseconds. - */ + // idle -- + // Should be called periodically by your application to give GGPO.net + // a chance to do some work. Most packet transmissions and rollbacks occur + // in idle. + // + // timeout - The amount of time GGPO.net is allowed to spend in this function, + // in milliseconds. idle :: proc(session: ^Session, timeout: c.int) -> ErrorCode --- - /* - * add_local_input -- - * - * Used to notify GGPO.net of inputs that should be trasmitted to remote - * players. add_local_input must be called once every frame for - * all player of type PLAYERTYPE_LOCAL. - * - * player - The player handle returned for this player when you called - * add_local_player. - * - * values - The controller inputs for this player. - * - * size - The size of the controller inputs. This must be exactly equal to the - * size passed into start_session. - */ + // add_local_input -- + // + // Used to notify GGPO.net of inputs that should be trasmitted to remote + // players. add_local_input must be called once every frame for + // all player of type PLAYERTYPE_LOCAL. + // + // player - The player handle returned for this player when you called + // add_local_player. + // + // values - The controller inputs for this player. + // + // size - The size of the controller inputs. This must be exactly equal to the + // size passed into start_session. add_local_input :: proc(session: ^Session, player: PlayerHandle, values: rawptr, size: c.int) -> ErrorCode --- - /* - * synchronize_input -- - * - * You should call synchronize_input before every frame of execution, - * including those frames which happen during rollback. - * - * values - When the function returns, the values parameter will contain - * inputs for this frame for all players. The values array must be at - * least (size * players) large. - * - * size - The size of the values array. - * - * disconnect_flags - Indicated whether the input in slot (1 << flag) is - * valid. If a player has disconnected, the input in the values array for - * that player will be zeroed and the i-th flag will be set. For example, - * if only player 3 has disconnected, disconnect flags will be 8 (i.e. 1 << 3). - */ + // synchronize_input -- + // + // You should call synchronize_input before every frame of execution, + // including those frames which happen during rollback. + // + // values - When the function returns, the values parameter will contain + // inputs for this frame for all players. The values array must be at + // least (size * players) large. + // + // size - The size of the values array. + // + // disconnect_flags - Indicated whether the input in slot (1 << flag) is + // valid. If a player has disconnected, the input in the values array for + // that player will be zeroed and the i-th flag will be set. For example, + // if only player 3 has disconnected, disconnect flags will be 8 (i.e. 1 << 3). synchronize_input :: proc(session: ^Session, values: rawptr, size: c.int, disconnect_flags: ^c.int) -> ErrorCode --- - /* - * disconnect_player -- - * - * Disconnects a remote player from a game. Will return ERRORCODE_PLAYER_DISCONNECTED - * if you try to disconnect a player who has already been disconnected. - */ + // disconnect_player -- + // + // Disconnects a remote player from a game. Will return ERRORCODE_PLAYER_DISCONNECTED + // if you try to disconnect a player who has already been disconnected. disconnect_player :: proc(session: ^Session, player: PlayerHandle) -> ErrorCode --- - /* - * advance_frame -- - * - * You should call advance_frame to notify GGPO.net that you have - * advanced your gamestate by a single frame. You should call this everytime - * you advance the gamestate by a frame, even during rollbacks. GGPO.net - * may call your save_state callback before this function returns. - */ + // advance_frame -- + // + // You should call advance_frame to notify GGPO.net that you have + // advanced your gamestate by a single frame. You should call this everytime + // you advance the gamestate by a frame, even during rollbacks. GGPO.net + // may call your save_state callback before this function returns. advance_frame :: proc(session: ^Session) -> ErrorCode --- - /* - * get_network_stats -- - * - * Used to fetch some statistics about the quality of the network connection. - * - * player - The player handle returned from the add_player function you used - * to add the remote player. - * - * stats - Out parameter to the network statistics. - */ + // get_network_stats -- + // + // Used to fetch some statistics about the quality of the network connection. + // + // player - The player handle returned from the add_player function you used + // to add the remote player. + // + // stats - Out parameter to the network statistics. get_network_stats :: proc(session: ^Session, player: PlayerHandle, stats: ^NetworkStats) -> ErrorCode --- - /* - * set_disconnect_timeout -- - * - * Sets the disconnect timeout. The session will automatically disconnect - * from a remote peer if it has not received a packet in the timeout window. - * You will be notified of the disconnect via a EVENTCODE_DISCONNECTED_FROM_PEER - * event. - * - * Setting a timeout value of 0 will disable automatic disconnects. - * - * timeout - The time in milliseconds to wait before disconnecting a peer. - */ + // set_disconnect_timeout -- + // + // Sets the disconnect timeout. The session will automatically disconnect + // from a remote peer if it has not received a packet in the timeout window. + // You will be notified of the disconnect via a EVENTCODE_DISCONNECTED_FROM_PEER + // event. + // + // Setting a timeout value of 0 will disable automatic disconnects. + // + // timeout - The time in milliseconds to wait before disconnecting a peer. set_disconnect_timeout :: proc(session: ^Session, timeout: c.int) -> ErrorCode --- - /* - * set_disconnect_notify_start -- - * - * The time to wait before the first EVENTCODE_NETWORK_INTERRUPTED timeout - * will be sent. - * - * timeout - The amount of time which needs to elapse without receiving a packet - * before the EVENTCODE_NETWORK_INTERRUPTED event is sent. - */ + // set_disconnect_notify_start -- + // + // The time to wait before the first EVENTCODE_NETWORK_INTERRUPTED timeout + // will be sent. + // + // timeout - The amount of time which needs to elapse without receiving a packet + // before the EVENTCODE_NETWORK_INTERRUPTED event is sent. set_disconnect_notify_start :: proc(session: ^Session, timeout: c.int) -> ErrorCode --- - /* - * log -- - * - * Used to write to the ggpo.net log. In the current versions of the - * SDK, a log file is only generated if the "quark.log" environment - * variable is set to 1. This will change in future versions of the - * SDK. - */ + // log -- + // + // Used to write to the ggpo.net log. In the current versions of the + // SDK, a log file is only generated if the "quark.log" environment + // variable is set to 1. This will change in future versions of the + // SDK. log :: proc(session: ^Session, fmt: cstring, #c_vararg args: ..any) --- - /* - * logv -- - * - * A varargs compatible version of log. See log for - * more details. - */ + + // logv -- + // + // A varargs compatible version of log. See log for + // more details. logv :: proc(session: ^Session, fmt: cstring, args: c.va_list) --- } \ No newline at end of file diff --git a/vendor/lua/5.4/lua.odin b/vendor/lua/5.4/lua.odin index 9be8fea55..a45de4d49 100644 --- a/vendor/lua/5.4/lua.odin +++ b/vendor/lua/5.4/lua.odin @@ -575,7 +575,7 @@ PRELOAD_TABLE :: "_PRELOAD" L_Reg :: struct { name: cstring, - func: CFunction, + func: CFunction, } L_NUMSIZES :: size_of(Integer)*16 + size_of(Number) diff --git a/vendor/miniaudio/decoding.odin b/vendor/miniaudio/decoding.odin index 4860680c9..f1fa279ac 100644 --- a/vendor/miniaudio/decoding.odin +++ b/vendor/miniaudio/decoding.odin @@ -69,9 +69,9 @@ decoder :: struct { outputSampleRate: u32, converter: data_converter, /* <-- Data conversion is achieved by running frames through this. */ pInputCache: rawptr, /* In input format. Can be null if it's not needed. */ - inputCacheCap: u64, /* The capacity of the input cache. */ - inputCacheConsumed: u64, /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ - inputCacheRemaining: u64, /* The number of valid frames remaining in the cahce. */ + inputCacheCap: u64, /* The capacity of the input cache. */ + inputCacheConsumed: u64, /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ + inputCacheRemaining: u64, /* The number of valid frames remaining in the cahce. */ allocationCallbacks: allocation_callbacks, data: struct #raw_union { vfs: struct { diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index 857e53ff5..eae804720 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -381,7 +381,7 @@ device_config :: struct { noPreSilencedOutputBuffer: b8, /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ noClip: b8, /* When set to true, the contents of the output buffer passed into the data callback will not be clipped after returning. Only applies when the playback sample format is f32. */ noDisableDenormals: b8, /* Do not disable denormals when firing the data callback. */ - noFixedSizedCallback: b8, /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ + noFixedSizedCallback: b8, /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ dataCallback: device_data_proc, notificationCallback: device_notification_proc, stopCallback: stop_proc, @@ -813,7 +813,7 @@ context_type :: struct { /*pa_mainloop**/ pMainLoop: rawptr, /*pa_context**/ pPulseContext: rawptr, pApplicationName: cstring, /* Set when the context is initialized. Used by devices for their local pa_context objects. */ - pServerName: cstring, /* Set when the context is initialized. Used by devices for their local pa_context objects. */ + pServerName: cstring, /* Set when the context is initialized. Used by devices for their local pa_context objects. */ } when SUPPORT_PULSEAUDIO else struct {}), jack: (struct { @@ -1140,7 +1140,7 @@ device :: struct { pulse: (struct { /*pa_mainloop**/ pMainLoop: rawptr, - /*pa_context**/ pPulseContext: rawptr, + /*pa_context**/ pPulseContext: rawptr, /*pa_stream**/ pStreamPlayback: rawptr, /*pa_stream**/ pStreamCapture: rawptr, } when SUPPORT_PULSEAUDIO else struct {}), diff --git a/vendor/portmidi/portmidi.odin b/vendor/portmidi/portmidi.odin index 662373cc4..58d7c2ec2 100644 --- a/vendor/portmidi/portmidi.odin +++ b/vendor/portmidi/portmidi.odin @@ -80,9 +80,10 @@ foreign lib { HasHostError :: proc(stream: Stream) -> b32 --- } -/** Translate portmidi error number into human readable message. - These strings are constants (set at compile time) so client has - no need to allocate storage +/** + Translate portmidi error number into human readable message. + These strings are constants (set at compile time) so client has + no need to allocate storage */ GetErrorText :: proc (errnum: Error) -> string { @(default_calling_convention="c") @@ -92,9 +93,10 @@ GetErrorText :: proc (errnum: Error) -> string { return string(Pm_GetErrorText(errnum)) } -/** Translate portmidi host error into human readable message. - These strings are computed at run time, so client has to allocate storage. - After this routine executes, the host error is cleared. +/** + Translate portmidi host error into human readable message. + These strings are computed at run time, so client has to allocate storage. + After this routine executes, the host error is cleared. */ GetHostErrorText :: proc (buf: []byte) -> string { @(default_calling_convention="c") @@ -133,8 +135,8 @@ foreign lib { /** - Timestamp is used to represent a millisecond clock with arbitrary - start time. The type is used for all MIDI timestampes and clocks. + Timestamp is used to represent a millisecond clock with arbitrary + start time. The type is used for all MIDI timestampes and clocks. */ Timestamp :: distinct i32 TimeProc :: proc "c" (time_info: rawptr) -> Timestamp @@ -258,47 +260,47 @@ foreign lib { /* Filter bit-mask definitions */ /** filter active sensing messages (0xFE): */ -FILT_ACTIVE :: 1 << 0x0E +FILT_ACTIVE :: 1 << 0x0E /** filter system exclusive messages (0xF0): */ -FILT_SYSEX :: 1 << 0x00 +FILT_SYSEX :: 1 << 0x00 /** filter MIDI clock message (0xF8) */ -FILT_CLOCK :: 1 << 0x08 +FILT_CLOCK :: 1 << 0x08 /** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ -FILT_PLAY :: (1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B) +FILT_PLAY :: (1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B) /** filter tick messages (0xF9) */ -FILT_TICK :: 1 << 0x09 +FILT_TICK :: 1 << 0x09 /** filter undefined FD messages */ -FILT_FD :: 1 << 0x0D +FILT_FD :: 1 << 0x0D /** filter undefined real-time messages */ -FILT_UNDEFINED :: FILT_FD +FILT_UNDEFINED :: FILT_FD /** filter reset messages (0xFF) */ -FILT_RESET :: 1 << 0x0F +FILT_RESET :: 1 << 0x0F /** filter all real-time messages */ -FILT_REALTIME :: FILT_ACTIVE | FILT_SYSEX | FILT_CLOCK | FILT_PLAY | FILT_UNDEFINED | FILT_RESET | FILT_TICK +FILT_REALTIME :: FILT_ACTIVE | FILT_SYSEX | FILT_CLOCK | FILT_PLAY | FILT_UNDEFINED | FILT_RESET | FILT_TICK /** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ -FILT_NOTE :: (1 << 0x19) | (1 << 0x18) +FILT_NOTE :: (1 << 0x19) | (1 << 0x18) /** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ FILT_CHANNEL_AFTERTOUCH :: 1 << 0x1D /** per-note aftertouch (0xA0-0xAF) */ -FILT_POLY_AFTERTOUCH :: 1 << 0x1A +FILT_POLY_AFTERTOUCH :: 1 << 0x1A /** filter both channel and poly aftertouch */ -FILT_AFTERTOUCH :: FILT_CHANNEL_AFTERTOUCH | FILT_POLY_AFTERTOUCH +FILT_AFTERTOUCH :: FILT_CHANNEL_AFTERTOUCH | FILT_POLY_AFTERTOUCH /** Program changes (0xC0-0xCF) */ -FILT_PROGRAM :: 1 << 0x1C +FILT_PROGRAM :: 1 << 0x1C /** Control Changes (CC's) (0xB0-0xBF)*/ -FILT_CONTROL :: 1 << 0x1B +FILT_CONTROL :: 1 << 0x1B /** Pitch Bender (0xE0-0xEF*/ -FILT_PITCHBEND :: 1 << 0x1E +FILT_PITCHBEND :: 1 << 0x1E /** MIDI Time Code (0xF1)*/ -FILT_MTC :: 1 << 0x01 +FILT_MTC :: 1 << 0x01 /** Song Position (0xF2) */ -FILT_SONG_POSITION :: 1 << 0x02 +FILT_SONG_POSITION :: 1 << 0x02 /** Song Select (0xF3)*/ -FILT_SONG_SELECT :: 1 << 0x03 +FILT_SONG_SELECT :: 1 << 0x03 /** Tuning request (0xF6)*/ -FILT_TUNE :: 1 << 0x06 +FILT_TUNE :: 1 << 0x06 /** All System Common messages (mtc, song position, song select, tune request) */ -FILT_SYSTEMCOMMON :: FILT_MTC | FILT_SONG_POSITION | FILT_SONG_SELECT | FILT_TUNE +FILT_SYSTEMCOMMON :: FILT_MTC | FILT_SONG_POSITION | FILT_SONG_SELECT | FILT_TUNE Channel :: #force_inline proc "c" (channel: c.int) -> c.int { return 1< Message { return Message(((data2 << 16) & 0xFF0000) | ((data1 << 8) & 0xFF00) | (status & 0xFF)) diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index c367f125c..59ffb02b8 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -110,6 +110,8 @@ GuiDefaultProperty :: enum c.int { LINE_COLOR, // Line control color BACKGROUND_COLOR, // Background color TEXT_LINE_SPACING, // Text spacing between lines + TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) + TEXT_WRAP_MODE, // Text wrap-mode inside text bounds } // Label @@ -163,11 +165,7 @@ GuiDropdownBoxProperty :: enum c.int { // TextBox/TextBoxMulti/ValueBox/Spinner GuiTextBoxProperty :: enum c.int { - TEXT_INNER_PADDING = 16, // TextBox/TextBoxMulti/ValueBox/Spinner inner text padding - TEXT_LINES_SPACING, // TextBoxMulti lines separation - TEXT_ALIGNMENT_VERTICAL, // TextBoxMulti vertical alignment: 0-CENTERED, 1-UP, 2-DOWN - TEXT_MULTILINE, // TextBox supports multiple lines - TEXT_WRAP_MODE, // TextBox wrap mode for multiline: 0-NO_WRAP, 1-CHAR_WRAP, 2-WORD_WRAP + TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable } // Spinner @@ -229,8 +227,8 @@ foreign lib { // Style set/get functions - 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 + GuiSetStyle :: proc(control: GuiControl, property: c.int, value: c.int) --- // Set one style property + GuiGetStyle :: proc(control: GuiControl, property: c.int) -> c.int --- // Get one style property // Styles loading functions diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index c7cb41019..88e45a25c 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -84,7 +84,6 @@ package raylib import "core:c" import "core:fmt" import "core:mem" -import "core:strings" import "core:math/linalg" _ :: linalg @@ -447,9 +446,9 @@ VrStereoConfig :: struct #align(4) { // File path list FilePathList :: struct { - capacity: c.uint, // Filepaths max entries - count: c.uint, // Filepaths entries count - paths: [^]cstring, // Filepaths entries + capacity: c.uint, // Filepaths max entries + count: c.uint, // Filepaths entries count + paths: [^]cstring, // Filepaths entries } // Automation event @@ -1029,7 +1028,6 @@ foreign lib { SetTraceLogLevel :: proc(logLevel: TraceLogLevel) --- // Set the current threshold (minimum) log level MemAlloc :: proc(size: c.uint) -> rawptr --- // Internal memory allocator MemRealloc :: proc(ptr: rawptr, size: c.uint) -> rawptr --- // Internal memory reallocator - MemFree :: proc(ptr: rawptr) --- // Internal memory free // Set custom callbacks // WARNING: Callbacks setup is intended for advance users @@ -1256,7 +1254,7 @@ foreign lib { LoadImage :: proc(fileName: cstring) -> Image --- // Load image from file into CPU memory (RAM) LoadImageRaw :: proc(fileName: cstring, width, height: c.int, format: PixelFormat, headerSize: c.int) -> Image --- // Load image from RAW file data LoadImageSvg :: proc(fileNameOrString: cstring, width, height: c.int) -> Image --- // Load image from SVG file data or string with specified size - LoadImageAnim :: proc(fileName: cstring, frames: [^]c.int) -> Image --- // Load image sequence from file (frames appended to image.data) + LoadImageAnim :: proc(fileName: cstring, frames: ^c.int) -> Image --- // Load image sequence from file (frames appended to image.data) LoadImageFromMemory :: proc(fileType: cstring, fileData: rawptr, dataSize: c.int) -> Image --- // Load image from memory buffer, fileType refers to extension: i.e. '.png' LoadImageFromTexture :: proc(texture: Texture2D) -> Image --- // Load image from GPU texture data LoadImageFromScreen :: proc() -> Image --- // Load image from screen buffer and (screenshot) @@ -1684,8 +1682,25 @@ 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 { - str := fmt.tprintf(string(text), ..args) - return strings.clone_to_cstring(str, MemAllocator()) + return fmt.caprintf(string(text), ..args, allocator=MemAllocator()) +} + + +// Internal memory free +MemFree :: proc{ + MemFreePtr, + MemFreeCstring, +} + + +@(default_calling_convention="c") +foreign lib { + @(link_name="MemFree") + MemFreePtr :: proc(ptr: rawptr) --- +} + +MemFreeCstring :: proc "c" (s: cstring) { + MemFreePtr(rawptr(s)) } diff --git a/vendor/sdl2/sdl2.odin b/vendor/sdl2/sdl2.odin index 73d95e18a..b23389a64 100644 --- a/vendor/sdl2/sdl2.odin +++ b/vendor/sdl2/sdl2.odin @@ -41,6 +41,12 @@ MAJOR_VERSION :: 2 MINOR_VERSION :: 0 PATCHLEVEL :: 16 +VERSION :: proc "contextless" (ver: ^version) { + ver.major = MAJOR_VERSION + ver.minor = MINOR_VERSION + ver.patch = PATCHLEVEL +} + @(default_calling_convention="c", link_prefix="SDL_") foreign lib { GetVersion :: proc(ver: ^version) --- diff --git a/vendor/sdl2/sdl_events.odin b/vendor/sdl2/sdl_events.odin index 60daaea56..b4c92683c 100644 --- a/vendor/sdl2/sdl_events.odin +++ b/vendor/sdl2/sdl_events.odin @@ -171,9 +171,9 @@ KeyboardEvent :: struct { TEXTEDITINGEVENT_TEXT_SIZE :: 32 TextEditingEvent :: struct { type: EventType, /**< ::SDL_TEXTEDITING */ - timestamp: u32, /**< In milliseconds, populated using SDL_GetTicks() */ - windowID: u32, /**< The window with keyboard focus, if any */ - text: [TEXTEDITINGEVENT_TEXT_SIZE]u8, /**< The editing text */ + timestamp: u32, /**< In milliseconds, populated using SDL_GetTicks() */ + windowID: u32, /**< The window with keyboard focus, if any */ + text: [TEXTEDITINGEVENT_TEXT_SIZE]u8, /**< The editing text */ start: i32, /**< The start cursor of selected editing text */ length: i32, /**< The length of selected editing text */ } @@ -184,7 +184,7 @@ TextInputEvent :: struct { type: EventType, /**< ::SDL_TEXTINPUT */ timestamp: u32, /**< In milliseconds, populated using SDL_GetTicks() */ windowID: u32, /**< The window with keyboard focus, if any */ - text: [TEXTINPUTEVENT_TEXT_SIZE]u8, /**< The input text */ + text: [TEXTINPUTEVENT_TEXT_SIZE]u8, /**< The input text */ } MouseMotionEvent :: struct { diff --git a/vendor/sdl2/sdl_gamecontroller.odin b/vendor/sdl2/sdl_gamecontroller.odin index 8772faa26..beb7d5ce7 100644 --- a/vendor/sdl2/sdl_gamecontroller.odin +++ b/vendor/sdl2/sdl_gamecontroller.odin @@ -54,29 +54,29 @@ GameControllerAxis :: enum c.int { } GameControllerButton :: enum c.int { - INVALID = -1, - A, - B, - X, - Y, - BACK, - GUIDE, - START, - LEFTSTICK, - RIGHTSTICK, - LEFTSHOULDER, - RIGHTSHOULDER, - DPAD_UP, - DPAD_DOWN, - DPAD_LEFT, - DPAD_RIGHT, - MISC1, /* Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button */ - PADDLE1, /* Xbox Elite paddle P1 */ - PADDLE2, /* Xbox Elite paddle P3 */ - PADDLE3, /* Xbox Elite paddle P2 */ - PADDLE4, /* Xbox Elite paddle P4 */ - TOUCHPAD, /* PS4/PS5 touchpad button */ - MAX, + INVALID = -1, + A, + B, + X, + Y, + BACK, + GUIDE, + START, + LEFTSTICK, + RIGHTSTICK, + LEFTSHOULDER, + RIGHTSHOULDER, + DPAD_UP, + DPAD_DOWN, + DPAD_LEFT, + DPAD_RIGHT, + MISC1, /* Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button */ + PADDLE1, /* Xbox Elite paddle P1 */ + PADDLE2, /* Xbox Elite paddle P3 */ + PADDLE3, /* Xbox Elite paddle P2 */ + PADDLE4, /* Xbox Elite paddle P4 */ + TOUCHPAD, /* PS4/PS5 touchpad button */ + MAX, } diff --git a/vendor/sdl2/sdl_touch.odin b/vendor/sdl2/sdl_touch.odin index f2a8cc695..f0ca69333 100644 --- a/vendor/sdl2/sdl_touch.odin +++ b/vendor/sdl2/sdl_touch.odin @@ -19,10 +19,10 @@ TouchDeviceType :: enum c.int { } Finger :: struct { - id: FingerID, - x: f32, - y: f32, - pressure: f32, + id: FingerID, + x: f32, + y: f32, + pressure: f32, } TOUCH_MOUSEID :: ~u32(0) diff --git a/vendor/stb/image/stb_image_resize.odin b/vendor/stb/image/stb_image_resize.odin index d407a1852..e22b587b2 100644 --- a/vendor/stb/image/stb_image_resize.odin +++ b/vendor/stb/image/stb_image_resize.odin @@ -166,7 +166,7 @@ datatype :: enum c.int { UINT32, FLOAT, - MAX_TYPES, + MAX_TYPES, } @(default_calling_convention="c", link_prefix="stbir_") diff --git a/vendor/stb/lib/darwin/stb_truetype.a b/vendor/stb/lib/darwin/stb_truetype.a index f871693d0..b55fbe5d3 100644 Binary files a/vendor/stb/lib/darwin/stb_truetype.a and b/vendor/stb/lib/darwin/stb_truetype.a differ diff --git a/vendor/stb/lib/stb_truetype.lib b/vendor/stb/lib/stb_truetype.lib index d4139c707..16ecf944d 100644 Binary files a/vendor/stb/lib/stb_truetype.lib and b/vendor/stb/lib/stb_truetype.lib differ diff --git a/vendor/stb/lib/stb_truetype_wasm.o b/vendor/stb/lib/stb_truetype_wasm.o index 15c4fa0d5..d3380e8a2 100644 Binary files a/vendor/stb/lib/stb_truetype_wasm.o and b/vendor/stb/lib/stb_truetype_wasm.o differ diff --git a/vendor/stb/src/Makefile b/vendor/stb/src/Makefile index 6123a95fa..b7217d528 100644 --- a/vendor/stb/src/Makefile +++ b/vendor/stb/src/Makefile @@ -8,7 +8,7 @@ endif wasm: mkdir -p ../lib - clang -c -Os --target=wasm32 -nostdlib stb_truetype_wasm.c -o ../lib/stb_truetype_wasm.o + $(CC) -c -Os --target=wasm32 -nostdlib stb_truetype_wasm.c -o ../lib/stb_truetype_wasm.o unix: mkdir -p ../lib diff --git a/vendor/stb/src/stb_truetype.c b/vendor/stb/src/stb_truetype.c index e44c22c89..05c23f583 100644 --- a/vendor/stb/src/stb_truetype.c +++ b/vendor/stb/src/stb_truetype.c @@ -1,5 +1,2 @@ -#define STB_RECT_PACK_IMPLEMENTATION -#include "stb_rect_pack.h" - #define STB_TRUETYPE_IMPLEMENTATION #include "stb_truetype.h" \ No newline at end of file diff --git a/vendor/stb/truetype/stb_truetype_wasm.odin b/vendor/stb/truetype/stb_truetype_wasm.odin index ff1d4fac5..472419ccb 100644 --- a/vendor/stb/truetype/stb_truetype_wasm.odin +++ b/vendor/stb/truetype/stb_truetype_wasm.odin @@ -65,14 +65,8 @@ ceil :: proc "c" (x: f64) -> f64 { return math.ceil(x) } 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") -} - +fmod :: proc "c" (x, y: f64) -> f64 { return math.mod(x, y) } @(require, linkage="strong", link_name="stbtt_cos") cos :: proc "c" (x: f64) -> f64 { return math.cos(x) } @(require, linkage="strong", link_name="stbtt_acos") diff --git a/vendor/wgpu/README.md b/vendor/wgpu/README.md index 8b2c95b5e..1022e8541 100644 --- a/vendor/wgpu/README.md +++ b/vendor/wgpu/README.md @@ -11,8 +11,8 @@ Have a look at the `example/` directory for the rendering of a basic triangle. ## Getting the wgpu-native libraries For native support (not the browser), some libraries are required. Fortunately this is -extremely easy, just download them from the [releases on GitHub](https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1), -the bindings are for v0.19.4.1 at the moment. +extremely easy, just download them from the [releases on GitHub](https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1), +the bindings are for v22.1.0.1 at the moment. These are expected in the `lib` folder under the same name as they are released (just unzipped). By default it will look for a static release version (`wgpu-OS-ARCH-release.a|lib`), diff --git a/vendor/wgpu/examples/glfw/main.odin b/vendor/wgpu/examples/glfw/main.odin index 39161311c..b57206371 100644 --- a/vendor/wgpu/examples/glfw/main.odin +++ b/vendor/wgpu/examples/glfw/main.odin @@ -158,15 +158,17 @@ frame :: proc "c" (dt: f32) { view = frame, loadOp = .Clear, storeOp = .Store, - clearValue = { r = 0, g = 1, b = 0, a = 1 }, + depthSlice = wgpu.DEPTH_SLICE_UNDEFINED, + clearValue = { 0, 1, 0, 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) + wgpu.RenderPassEncoderRelease(render_pass_encoder) command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil) defer wgpu.CommandBufferRelease(command_buffer) diff --git a/vendor/wgpu/examples/sdl2/main.odin b/vendor/wgpu/examples/sdl2/main.odin index 3d79346d0..58e357f7a 100644 --- a/vendor/wgpu/examples/sdl2/main.odin +++ b/vendor/wgpu/examples/sdl2/main.odin @@ -158,15 +158,17 @@ frame :: proc "c" (dt: f32) { view = frame, loadOp = .Clear, storeOp = .Store, - clearValue = { r = 0, g = 1, b = 0, a = 1 }, + depthSlice = wgpu.DEPTH_SLICE_UNDEFINED, + clearValue = { 0, 1, 0, 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) + wgpu.RenderPassEncoderRelease(render_pass_encoder) command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil) defer wgpu.CommandBufferRelease(command_buffer) diff --git a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll index 650260bfc..b8f782a2f 100644 Binary files a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll and b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll differ diff --git a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll.lib b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll.lib index b838c2695..864b7eb53 100644 Binary files a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll.lib and b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.dll.lib differ diff --git a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.lib b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.lib index ea4044d81..7c9f95c33 100644 Binary files a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.lib and b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.lib differ diff --git a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.pdb b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.pdb index b30090276..52865e174 100644 Binary files a/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.pdb and b/vendor/wgpu/lib/wgpu-windows-x86_64-release/wgpu_native.pdb differ diff --git a/vendor/wgpu/sdl2glue/glue_linux.odin b/vendor/wgpu/sdl2glue/glue_linux.odin index b01df251a..58ec90499 100644 --- a/vendor/wgpu/sdl2glue/glue_linux.odin +++ b/vendor/wgpu/sdl2glue/glue_linux.odin @@ -5,7 +5,9 @@ import "vendor:wgpu" GetSurface :: proc(instance: wgpu.Instance, window: ^sdl2.Window) -> wgpu.Surface { window_info: sdl2.SysWMinfo + sdl2.VERSION(&window_info.version) sdl2.GetWindowWMInfo(window, &window_info) + if window_info.subsystem == .WAYLAND { display := window_info.info.wl.display surface := window_info.info.wl.surface diff --git a/vendor/wgpu/wgpu.js b/vendor/wgpu/wgpu.js index 4fe78c992..c100808e3 100644 --- a/vendor/wgpu/wgpu.js +++ b/vendor/wgpu/wgpu.js @@ -44,6 +44,7 @@ class WebGPUInterface { BlendFactor: ["zero", "one", "src", "one-minus-src", "src-alpha", "one-minus-src-alpha", "dst", "one-minus-dst", "dst-alpha", "one-minus-dst-alpha", "src-alpha-saturated", "constant", "one-minus-constant", ], PresentMode: ["fifo", "fifo-relaxed", "immediate", "mailbox", ], TextureAspect: ["all", "stencil-only", "depth-only"], + DeviceLostReason: [undefined, "unknown", "destroyed"], }; /** @type {WebGPUObjectManager<{}>} */ @@ -382,13 +383,19 @@ class WebGPUInterface { */ RenderPassColorAttachment(start) { const viewIdx = this.mem.loadPtr(start + 4); - const resolveTargetIdx = this.mem.loadPtr(start + 8); + const resolveTargetIdx = this.mem.loadPtr(start + 12); + + let depthSlice = this.mem.loadU32(start + 8); + if (depthSlice == 0xFFFFFFFF) { // DEPTH_SLICE_UNDEFINED. + depthSlice = undefined; + } return { view: viewIdx > 0 ? this.textureViews.get(viewIdx) : undefined, resolveTarget: resolveTargetIdx > 0 ? this.textureViews.get(resolveTargetIdx) : undefined, - loadOp: this.enumeration("LoadOp", start + 12), - storeOp: this.enumeration("StoreOp", start + 16), + depthSlice: depthSlice, + loadOp: this.enumeration("LoadOp", start + 16), + storeOp: this.enumeration("StoreOp", start + 20), clearValue: this.Color(start + 24), }; } @@ -950,14 +957,25 @@ class WebGPUInterface { /** * @param {number} adapterIdx - * @param {number} propertiesPtr + * @param {number} infoPtr */ - wgpuAdapterGetProperties: (adapterIdx, propertiesPtr) => { - this.assert(propertiesPtr != 0); - // Unknown adapter. - this.mem.storeI32(propertiesPtr + 28, 3); + wgpuAdapterGetInfo: (adapterIdx, infoPtr) => { + this.assert(infoPtr != 0); + // WebGPU backend. - this.mem.storeI32(propertiesPtr + 32, 2); + this.mem.storeI32(infoPtr + 20, 2); + // Unknown adapter. + this.mem.storeI32(infoPtr + 24, 3); + + // NOTE: I don't think getting the other fields in this struct is possible. + // `adapter.requestAdapterInfo` is deprecated. + }, + + /** + * @param {number} infoPtr + */ + wgpuAdapterInfoFreeMembers: (infoPtr) => { + // NOTE: nothing to free. }, /** @@ -970,50 +988,6 @@ class WebGPUInterface { return adapter.features.has(this.enums.FeatureName[featureInt]); }, - /** - * @param {number} adapterIdx - * @param {number} callbackPtr - * @param {0|number} userdata - */ - wgpuAdapterRequestAdapterInfo: async (adapterIdx, callbackPtr, userdata) => { - const adapter = this.adapters.get(adapterIdx); - const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); - - const info = await adapter.requestAdapterInfo(); - - const addr = this.mem.exports.wgpu_alloc(16); - - const vendorLength = new TextEncoder().encode(info.vendor).length; - const vendorAddr = this.mem.exports.wgpu_alloc(vendorLength); - this.mem.storeString(vendorAddr, info.vendor); - this.mem.storeI32(addr + 0, vendorAddr); - - const architectureLength = new TextEncoder().encode(info.architecture).length; - const architectureAddr = this.mem.exports.wgpu_alloc(architectureLength); - this.mem.storeString(architectureAddr, info.architecture); - this.mem.storeI32(addr + 4, architectureAddr); - - - const deviceLength = new TextEncoder().encode(info.device).length; - const deviceAddr = this.mem.exports.wgpu_alloc(deviceLength); - this.mem.storeString(deviceAddr, info.device); - this.mem.storeI32(addr + 8, deviceAddr); - - - const descriptionLength = new TextEncoder().encode(info.description).length; - const descriptionAddr = this.mem.exports.wgpu_alloc(descriptionLength); - this.mem.storeString(descriptionAddr, info.description); - this.mem.storeI32(addr + 12, descriptionAddr); - - callback(addr, userdata); - - this.mem.exports.wgpu_free(descriptionAddr); - this.mem.exports.wgpu_free(deviceAddr); - this.mem.exports.wgpu_free(architectureAddr); - this.mem.exports.wgpu_free(vendorAddr); - this.mem.exports.wgpu_free(addr); - }, - /** * @param {number} adapterIdx * @param {0|number} descriptorPtr @@ -1040,14 +1014,69 @@ class WebGPUInterface { }; } + let device; let deviceIdx; try { - const device = await adapter.requestDevice(descriptor); + device = await adapter.requestDevice(descriptor); deviceIdx = this.devices.create(device); // NOTE: don't callback here, any errors that happen later will then be caught by the catch here. } catch (e) { - console.warn(e); - callback(1, null, null, userdata); + const messageLength = new TextEncoder().encode(e.message).length; + const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1); + this.mem.storeString(messageAddr, e.message); + + callback(1, null, messageAddr, userdata); + + this.mem.exports.wgpu_free(messageAddr); + } + + let callbacksPtr = descriptorPtr + 24 + this.mem.intSize; + + const deviceLostCallbackPtr = this.mem.loadPtr(callbacksPtr); + if (deviceLostCallbackPtr != 0) { + const deviceLostUserData = this.mem.loadPtr(callbacksPtr) + 4; + const deviceLostCallback = this.mem.exports.__indirect_function_table.get(deviceLostCallbackPtr); + + device.lost.then((info) => { + const reason = this.enums.DeviceLostReason.indexOf(info.reason); + + const messageLength = new TextEncoder().encode(info.message).length; + const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1); + this.mem.storeString(messageAddr, info.message); + + deviceLostCallback(reason, messageAddr, deviceLostUserData); + + this.mem.exports.wgpu_free(messageAddr); + }); + } + callbacksPtr += 8; + + // Skip over `nextInChain`. + callbacksPtr += 4; + + const uncapturedErrorCallbackPtr = this.mem.loadPtr(callbacksPtr); + if (uncapturedErrorCallbackPtr != 0) { + const uncapturedErrorUserData = this.mem.loadPtr(callbacksPtr + 4); + const uncapturedErrorCallback = this.mem.exports.__indirect_function_table.get(uncapturedErrorCallbackPtr); + + device.onuncapturederror = (ev) => { + let status = 4; // Unknown + if (ev.error instanceof GPUValidationError) { + status = 1; // Validation + } else if (ev.error instanceof GPUOutOfMemoryError) { + status = 2; // OutOfMemory + } else if (ev.error instanceof GPUInternalError) { + status = 3; // Internal + } + + const messageLength = new TextEncoder().encode(ev.error.message).length; + const messageAddr = this.mem.exports.wgpu_alloc(messageLength + 1); + this.mem.storeString(messageAddr, ev.error.message); + + uncapturedErrorCallback(status, messageAddr, uncapturedErrorUserData); + + this.mem.exports.wgpu_free(messageAddr); + }; } callback(0, deviceIdx, null, userdata); @@ -1918,29 +1947,6 @@ class WebGPUInterface { device.pushErrorScope(this.enums.ErrorFilter[filterInt]); }, - /** - * @param {number} deviceIdx - * @param {number} callbackPtr - * @param {number} userdata - */ - wgpuDeviceSetUncapturedErrorCallback: (deviceIdx, callbackPtr, userdata) => { - const device = this.devices.get(deviceIdx); - const callback = this.mem.exports.__indirect_function_table.get(callbackPtr); - - device.onuncapturederror = (ev) => { - console.warn(ev.error); - let status = 4; - if (error instanceof GPUValidationError) { - status = 1; - } else if (error instanceof GPUOutOfMemoryError) { - status = 2; - } else if (error instanceof GPUInternalError) { - status = 3; - } - callback(status, null, userdata); - }; - }, - ...this.devices.interface(true), /* ---------------------- Instance ---------------------- */ @@ -2646,23 +2652,23 @@ class WebGPUInterface { const formatStr = navigator.gpu.getPreferredCanvasFormat(); const format = this.enums.TextureFormat.indexOf(formatStr); - this.mem.storeUint(capabilitiesPtr + this.mem.intSize, 1); + this.mem.storeUint(capabilitiesPtr + 8, 1); const formatAddr = this.mem.exports.wgpu_alloc(4); this.mem.storeI32(formatAddr, format); - this.mem.storeI32(capabilitiesPtr + this.mem.intSize*2, formatAddr); + this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize, formatAddr); // NOTE: present modes don't seem to actually do anything in JS, we can just give back a default FIFO though. - this.mem.storeUint(capabilitiesPtr + this.mem.intSize*3, 1); + this.mem.storeUint(capabilitiesPtr + 8 + this.mem.intSize*2, 1); const presentModesAddr = this.mem.exports.wgpu_alloc(4); this.mem.storeI32(presentModesAddr, 0); - this.mem.storeI32(capabilitiesPtr + this.mem.intSize*4, presentModesAddr); + this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize*3, presentModesAddr); // Browser seems to support opaque (1) and premultiplied (2). - this.mem.storeUint(capabilitiesPtr + this.mem.intSize*5, 2); + this.mem.storeUint(capabilitiesPtr + 8 + this.mem.intSize*4, 2); const alphaModesAddr = this.mem.exports.wgpu_alloc(8); this.mem.storeI32(alphaModesAddr + 0, 1); // Opaque. this.mem.storeI32(alphaModesAddr + 4, 2); // premultiplied. - this.mem.storeI32(capabilitiesPtr + this.mem.intSize*6, alphaModesAddr); + this.mem.storeI32(capabilitiesPtr + 8 + this.mem.intSize*5, alphaModesAddr); }, /** @@ -2680,17 +2686,6 @@ class WebGPUInterface { // TODO: determine suboptimal and/or status. }, - /** - * @param {number} surfaceIdx - * @param {number} texturePtr - * @returns {number} - */ - wgpuSurfaceGetPreferredFormat: (surfaceIdx, adapterIdx) => { - const formatStr = navigator.gpu.getPreferredCanvasFormat(); - const format = this.enums.TextureFormat.indexOf(formatStr); - return format; - }, - /** * @param {number} surfaceIdx */ diff --git a/vendor/wgpu/wgpu.odin b/vendor/wgpu/wgpu.odin index af81dde56..ae4649aed 100644 --- a/vendor/wgpu/wgpu.odin +++ b/vendor/wgpu/wgpu.odin @@ -13,7 +13,7 @@ when ODIN_OS == .Windows { @(private) LIB :: "lib/wgpu-windows-" + ARCH + "-" + TYPE + "/wgpu_native" + EXT when !#exists(LIB) { - #panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'") + #panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1, make sure to read the README at '" + #directory + "README.md'") } foreign import libwgpu { @@ -27,6 +27,8 @@ when ODIN_OS == .Windows { "system:advapi32.lib", "system:user32.lib", "system:gdi32.lib", + "system:ole32.lib", + "system:oleaut32.lib", } } else when ODIN_OS == .Darwin { @(private) ARCH :: "x86_64" when ODIN_ARCH == .amd64 else "aarch64" when ODIN_ARCH == .arm64 else #panic("unsupported WGPU Native architecture") @@ -34,7 +36,7 @@ when ODIN_OS == .Windows { @(private) LIB :: "lib/wgpu-macos-" + ARCH + "-" + TYPE + "/libwgpu_native" + EXT when !#exists(LIB) { - #panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'") + #panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1, make sure to read the README at '" + #directory + "README.md'") } foreign import libwgpu { @@ -49,7 +51,7 @@ when ODIN_OS == .Windows { @(private) LIB :: "lib/wgpu-linux-" + ARCH + "-" + TYPE + "/libwgpu_native" + EXT when !#exists(LIB) { - #panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1, make sure to read the README at '" + #directory + "vendor/wgpu/README.md'") + #panic("Could not find the compiled WGPU Native library at '" + #directory + LIB + "', these can be downloaded from https://github.com/gfx-rs/wgpu-native/releases/tag/v22.1.0.1, make sure to read the README at '" + #directory + "README.md'") } foreign import libwgpu { @@ -220,7 +222,8 @@ CullMode :: enum i32 { DeviceLostReason :: enum i32 { Undefined = 0x00000000, - Destroyed = 0x00000001, + Unknown = 0x00000001, + Destroyed = 0x00000002, } ErrorFilter :: enum i32 { @@ -264,6 +267,30 @@ FeatureName :: enum i32 { PipelineStatisticsQuery, StorageResourceBindingArray, PartiallyBoundBindingArray, + TextureFormat16bitNorm, + TextureCompressionAstcHdr, + // TODO: requires wgpu.h api change + // TimestampQueryInsidePasses, + MappablePrimaryBuffers = 0x0003000E, + BufferBindingArray, + UniformBufferAndStorageTextureArrayNonUniformIndexing, + // TODO: requires wgpu.h api change + // AddressModeClampToZero, + // AddressModeClampToBorder, + // PolygonModeLine, + // PolygonModePoint, + // ConservativeRasterization, + // ClearTexture, + // SprivShaderPassThrough, + // MultiView, + VertexAttribute64bit = 0x00030019, + TextureFormatNv12, + RayTracingAccelarationStructure, + RayQuery, + ShaderF64, + ShaderI16, + ShaderPrimitiveIndex, + ShaderEarlyDepthTest, } FilterMode :: enum i32 { @@ -520,6 +547,18 @@ TextureFormat :: enum i32 { ASTC12x10UnormSrgb = 0x0000005D, ASTC12x12Unorm = 0x0000005E, ASTC12x12UnormSrgb = 0x0000005F, + + // Native. + + // From FeatureName.TextureFormat16bitNorm + R16Unorm = 0x00030001, + R16Snorm, + Rg16Unorm, + Rg16Snorm, + Rgba16Unorm, + Rgba16Snorm, + // From FeatureName.TextureFormatNv12 + NV12, } TextureSampleType :: enum i32 { @@ -581,13 +620,13 @@ VertexStepMode :: enum i32 { VertexBufferNotUsed = 0x00000002, } -// WGSLFeatureName :: enum i32 { -// Undefined = 0x00000000, -// ReadonlyAndReadwriteStorageTextures = 0x00000001, -// Packed4x8IntegerDotProduct = 0x00000002, -// UnrestrictedPointerParameters = 0x00000003, -// PointerCompositeAccess = 0x00000004, -// } +WGSLFeatureName :: enum i32 { + Undefined = 0x00000000, + ReadonlyAndReadwriteStorageTextures = 0x00000001, + Packed4x8IntegerDotProduct = 0x00000002, + UnrestrictedPointerParameters = 0x00000003, + PointerCompositeAccess = 0x00000004, +} BufferUsage :: enum i32 { MapRead = 0x00000000, @@ -634,22 +673,18 @@ TextureUsage :: enum i32 { } TextureUsageFlags :: bit_set[TextureUsage; Flags] - -BufferMapAsyncCallback :: #type proc "c" (status: BufferMapAsyncStatus, /* NULLABLE */ userdata: rawptr) -ShaderModuleGetCompilationInfoCallback :: #type proc "c" (status: CompilationInfoRequestStatus, compilationInfo: ^CompilationInfo, /* NULLABLE */ userdata: rawptr) -DeviceCreateComputePipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: ComputePipeline, message: cstring, /* NULLABLE */ userdata: rawptr) -DeviceCreateRenderPipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: RenderPipeline, message: cstring, /* NULLABLE */ userdata: rawptr) +Proc :: distinct rawptr DeviceLostCallback :: #type proc "c" (reason: DeviceLostReason, message: cstring, userdata: rawptr) ErrorCallback :: #type proc "c" (type: ErrorType, message: cstring, userdata: rawptr) -Proc :: distinct rawptr - -QueueOnSubmittedWorkDoneCallback :: #type proc "c" (status: QueueWorkDoneStatus, /* NULLABLE */ userdata: rawptr) -InstanceRequestAdapterCallback :: #type proc "c" (status: RequestAdapterStatus, adapter: Adapter, message: cstring, /* NULLABLE */ userdata: rawptr) AdapterRequestDeviceCallback :: #type proc "c" (status: RequestDeviceStatus, device: Device, message: cstring, /* NULLABLE */ userdata: rawptr) - -// AdapterRequestAdapterInfoCallback :: #type proc "c" (adapterInfo: AdapterInfo, /* NULLABLE */ userdata: rawptr) +BufferMapAsyncCallback :: #type proc "c" (status: BufferMapAsyncStatus, /* NULLABLE */ userdata: rawptr) +DeviceCreateComputePipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: ComputePipeline, message: cstring, /* NULLABLE */ userdata: rawptr) +DeviceCreateRenderPipelineAsyncCallback :: #type proc "c" (status: CreatePipelineAsyncStatus, pipeline: RenderPipeline, message: cstring, /* NULLABLE */ userdata: rawptr) +InstanceRequestAdapterCallback :: #type proc "c" (status: RequestAdapterStatus, adapter: Adapter, message: cstring, /* NULLABLE */ userdata: rawptr) +QueueOnSubmittedWorkDoneCallback :: #type proc "c" (status: QueueWorkDoneStatus, /* NULLABLE */ userdata: rawptr) +ShaderModuleGetCompilationInfoCallback :: #type proc "c" (status: CompilationInfoRequestStatus, compilationInfo: ^CompilationInfo, /* NULLABLE */ userdata: rawptr) ChainedStruct :: struct { next: ^ChainedStruct, @@ -661,28 +696,23 @@ ChainedStructOut :: struct { sType: SType, } -// AdapterInfo :: struct { -// next: ^ChainedStructOut, -// vendor: cstring, -// architecture: cstring, -// device: cstring, -// description: cstring, -// backendType: BackendType, -// adapterType: AdapterType, -// vendorID: u32, -// deviceID: u32, -// } - -AdapterProperties :: struct { +AdapterInfo :: struct { nextInChain: ^ChainedStructOut, - vendorID: u32, - vendorName: cstring, + vendor: cstring, architecture: cstring, - deviceID: u32, - name: cstring, - driverDescription: cstring, - adapterType: AdapterType, + device: cstring, + description: cstring, backendType: BackendType, + adapterType: AdapterType, + vendorID: u32, + deviceID: u32, +} +when ODIN_OS == .JS { + #assert(int(BackendType.WebGPU) == 2) + #assert(offset_of(AdapterInfo, backendType) == 20) + + #assert(int(AdapterType.Unknown) == 3) + #assert(offset_of(AdapterInfo, adapterType) == 24) } BindGroupEntry :: struct { @@ -943,6 +973,7 @@ StorageTextureBindingLayout :: struct { SurfaceCapabilities :: struct { nextInChain: ^ChainedStructOut, + usages: TextureUsageFlags, formatCount: uint, formats: /* const */ [^]TextureFormat `fmt:"v,formatCount"`, presentModeCount: uint, @@ -950,6 +981,16 @@ SurfaceCapabilities :: struct { alphaModeCount: uint, alphaModes: /* const */ [^]CompositeAlphaMode `fmt:"v,alphaModeCount"`, } +when ODIN_OS == .JS { + #assert(offset_of(SurfaceCapabilities, formatCount) == 8) + #assert(offset_of(SurfaceCapabilities, formats) == 8 + 1*size_of(int)) + + #assert(offset_of(SurfaceCapabilities, presentModeCount) == 8 + 2*size_of(int)) + #assert(offset_of(SurfaceCapabilities, presentModes) == 8 + 3*size_of(int)) + + #assert(offset_of(SurfaceCapabilities, alphaModeCount) == 8 + 4*size_of(int)) + #assert(offset_of(SurfaceCapabilities, alphaModes) == 8 + 5*size_of(int)) +} SurfaceConfiguration :: struct { nextInChain: ^ChainedStruct, @@ -1040,6 +1081,12 @@ TextureViewDescriptor :: struct { aspect: TextureAspect, } +UncapturedErrorCallbackInfo :: struct { + nextInChain: ^ChainedStruct, + callback: ErrorCallback, + userdata: rawptr, +} + VertexAttribute :: struct { format: VertexFormat, offset: u64, @@ -1120,12 +1167,21 @@ ProgrammableStageDescriptor :: struct { RenderPassColorAttachment :: struct { nextInChain: ^ChainedStruct, /* NULLABLE */ view: TextureView, - // depthSlice: u32, + depthSlice: u32, /* NULLABLE */ resolveTarget: TextureView, loadOp: LoadOp, storeOp: StoreOp, clearValue: Color, } +when ODIN_OS == .JS { + #assert(size_of(RenderPassColorAttachment) == 56) + #assert(offset_of(RenderPassColorAttachment, view) == 4) + #assert(offset_of(RenderPassColorAttachment, depthSlice) == 8) + #assert(offset_of(RenderPassColorAttachment, resolveTarget) == 12) + #assert(offset_of(RenderPassColorAttachment, loadOp) == 16) + #assert(offset_of(RenderPassColorAttachment, storeOp) == 20) + #assert(offset_of(RenderPassColorAttachment, clearValue) == 24) +} RequiredLimits :: struct { nextInChain: ^ChainedStruct, @@ -1194,6 +1250,10 @@ DeviceDescriptor :: struct { defaultQueue: QueueDescriptor, deviceLostCallback: DeviceLostCallback, deviceLostUserdata: rawptr, + uncapturedErrorCallbackInfo: UncapturedErrorCallbackInfo, +} +when ODIN_OS == .JS { + #assert(offset_of(DeviceDescriptor, deviceLostCallback) == 24 + size_of(int)) } RenderPassDescriptor :: struct { @@ -1245,16 +1305,18 @@ foreign libwgpu { // Methods of Adapter @(link_name="wgpuAdapterEnumerateFeatures") RawAdapterEnumerateFeatures :: proc(adapter: Adapter, features: [^]FeatureName) -> uint --- + @(link_name="wgpuAdapterGetInfo") + RawAdapterGetInfo :: proc(adapter: Adapter, info: ^AdapterInfo) --- @(link_name="wgpuAdapterGetLimits") RawAdapterGetLimits :: proc(adapter: Adapter, limits: ^SupportedLimits) -> b32 --- - @(link_name="wgpuAdapterGetProperties") - RawAdapterGetProperties :: proc(adapter: Adapter, properties: ^AdapterProperties) --- AdapterHasFeature :: proc(adapter: Adapter, feature: FeatureName) -> b32 --- - // AdapterRequestAdapterInfo :: proc(adapter: Adapter, callback: AdapterRequestAdapterInfoCallback, /* NULLABLE */ userdata: rawptr) --- AdapterRequestDevice :: proc(adapter: Adapter, /* NULLABLE */ descriptor: /* const */ ^DeviceDescriptor, callback: AdapterRequestDeviceCallback, /* NULLABLE */ userdata: rawptr = nil) --- AdapterReference :: proc(adapter: Adapter) --- AdapterRelease :: proc(adapter: Adapter) --- + // Procs of AdapterInfo + AdapterInfoFreeMembers :: proc(adapterInfo: AdapterInfo) --- + // Methods of BindGroup BindGroupSetLabel :: proc(bindGroup: BindGroup, label: cstring) --- BindGroupReference :: proc(bindGroup: BindGroup) --- @@ -1348,13 +1410,12 @@ foreign libwgpu { DevicePopErrorScope :: proc(device: Device, callback: ErrorCallback, userdata: rawptr) --- DevicePushErrorScope :: proc(device: Device, filter: ErrorFilter) --- DeviceSetLabel :: proc(device: Device, label: cstring) --- - DeviceSetUncapturedErrorCallback :: proc(device: Device, callback: ErrorCallback, userdata: rawptr) --- DeviceReference :: proc(device: Device) --- DeviceRelease :: proc(device: Device) --- // Methods of Instance InstanceCreateSurface :: proc(instance: Instance, descriptor: /* const */ ^SurfaceDescriptor) -> Surface --- - // InstanceHasWGSLLanguageFeature :: proc(instance: Instance, feature: WGSLFeatureName) -> b32 --- + InstanceHasWGSLLanguageFeature :: proc(instance: Instance, feature: WGSLFeatureName) -> b32 --- InstanceProcessEvents :: proc(instance: Instance) --- InstanceRequestAdapter :: proc(instance: Instance, /* NULLABLE */ options: /* const */ ^RequestAdapterOptions, callback: InstanceRequestAdapterCallback, /* NULLABLE */ userdata: rawptr = nil) --- InstanceReference :: proc(instance: Instance) --- @@ -1455,9 +1516,8 @@ foreign libwgpu { RawSurfaceGetCapabilities :: proc(surface: Surface, adapter: Adapter, capabilities: ^SurfaceCapabilities) --- @(link_name="wgpuSurfaceGetCurrentTexture") RawSurfaceGetCurrentTexture :: proc(surface: Surface, surfaceTexture: ^SurfaceTexture) --- - SurfaceGetPreferredFormat :: proc(surface: Surface, adapter: Adapter) -> TextureFormat --- SurfacePresent :: proc(surface: Surface) --- - // SurfaceSetLabel :: proc(surface: Surface, label: cstring) --- + SurfaceSetLabel :: proc(surface: Surface, label: cstring) --- SurfaceUnconfigure :: proc(surface: Surface) --- SurfaceReference :: proc(surface: Surface) --- SurfaceRelease :: proc(surface: Surface) --- @@ -1500,8 +1560,8 @@ AdapterGetLimits :: proc(adapter: Adapter) -> (limits: SupportedLimits, ok: bool return } -AdapterGetProperties :: proc(adapter: Adapter) -> (properties: AdapterProperties) { - RawAdapterGetProperties(adapter, &properties) +AdapterGetInfo :: proc(adapter: Adapter) -> (info: AdapterInfo) { + RawAdapterGetInfo(adapter, &info) return } @@ -1634,8 +1694,8 @@ SurfaceGetCurrentTexture :: proc(surface: Surface) -> (surface_texture: SurfaceT // WGPU Native bindings -BINDINGS_VERSION :: [4]u8{0, 19, 4, 1} -BINDINGS_VERSION_STRING :: "0.19.4.1" +BINDINGS_VERSION :: [4]u8{22, 1, 0, 1} +BINDINGS_VERSION_STRING :: "22.1.0.1" when ODIN_OS != .JS { @(private="file", init) diff --git a/vendor/x11/xlib/xlib_const.odin b/vendor/x11/xlib/xlib_const.odin index ce31a4e76..0466df76d 100644 --- a/vendor/x11/xlib/xlib_const.odin +++ b/vendor/x11/xlib/xlib_const.odin @@ -72,6 +72,11 @@ XkbAllEventsMask :: XkbEventMask { .ExtensionDeviceNotify, } +/* ---- X11/extensions/XI2.h ---------------------------------------------------------*/ + +XIAllDevices :: 0 +XIAllMasterDevices :: 1 + /* ---- X11/Xlib.h ---------------------------------------------------------*/ diff --git a/vendor/x11/xlib/xlib_keysym.odin b/vendor/x11/xlib/xlib_keysym.odin index 594d966a4..acb16c530 100644 --- a/vendor/x11/xlib/xlib_keysym.odin +++ b/vendor/x11/xlib/xlib_keysym.odin @@ -2,1680 +2,1680 @@ package xlib KeySym :: enum u32 { - XK_BackSpace = 0xff08, /* Back space, back char */ - XK_Tab = 0xff09, - XK_Linefeed = 0xff0a, /* Linefeed, LF */ - XK_Clear = 0xff0b, - XK_Return = 0xff0d, /* Return, enter */ - XK_Pause = 0xff13, /* Pause, hold */ - XK_Scroll_Lock = 0xff14, - XK_Sys_Req = 0xff15, - XK_Escape = 0xff1b, - XK_Delete = 0xffff, /* Delete, rubout */ - XK_Multi_key = 0xff20, /* Multi-key character compose */ - XK_Codeinput = 0xff37, - XK_SingleCandidate = 0xff3c, - XK_MultipleCandidate = 0xff3d, - XK_PreviousCandidate = 0xff3e, - XK_Kanji = 0xff21, /* Kanji, Kanji convert */ - XK_Muhenkan = 0xff22, /* Cancel Conversion */ - XK_Henkan_Mode = 0xff23, /* Start/Stop Conversion */ - XK_Henkan = 0xff23, /* Alias for Henkan_Mode */ - XK_Romaji = 0xff24, /* to Romaji */ - XK_Hiragana = 0xff25, /* to Hiragana */ - XK_Katakana = 0xff26, /* to Katakana */ - XK_Hiragana_Katakana = 0xff27, /* Hiragana/Katakana toggle */ - XK_Zenkaku = 0xff28, /* to Zenkaku */ - XK_Hankaku = 0xff29, /* to Hankaku */ - XK_Zenkaku_Hankaku = 0xff2a, /* Zenkaku/Hankaku toggle */ - XK_Touroku = 0xff2b, /* Add to Dictionary */ - XK_Massyo = 0xff2c, /* Delete from Dictionary */ - XK_Kana_Lock = 0xff2d, /* Kana Lock */ - XK_Kana_Shift = 0xff2e, /* Kana Shift */ - XK_Eisu_Shift = 0xff2f, /* Alphanumeric Shift */ - XK_Eisu_toggle = 0xff30, /* Alphanumeric toggle */ - XK_Kanji_Bangou = 0xff37, /* Codeinput */ - XK_Zen_Koho = 0xff3d, /* Multiple/All Candidate(s) */ - XK_Mae_Koho = 0xff3e, /* Previous Candidate */ - XK_Home = 0xff50, - XK_Left = 0xff51, /* Move left, left arrow */ - XK_Up = 0xff52, /* Move up, up arrow */ - XK_Right = 0xff53, /* Move right, right arrow */ - XK_Down = 0xff54, /* Move down, down arrow */ - XK_Prior = 0xff55, /* Prior, previous */ - XK_Page_Up = 0xff55, - XK_Next = 0xff56, /* Next */ - XK_Page_Down = 0xff56, - XK_End = 0xff57, /* EOL */ - XK_Begin = 0xff58, /* BOL */ - XK_Select = 0xff60, /* Select, mark */ - XK_Print = 0xff61, - XK_Execute = 0xff62, /* Execute, run, do */ - XK_Insert = 0xff63, /* Insert, insert here */ - XK_Undo = 0xff65, - XK_Redo = 0xff66, /* Redo, again */ - XK_Menu = 0xff67, - XK_Find = 0xff68, /* Find, search */ - XK_Cancel = 0xff69, /* Cancel, stop, abort, exit */ - XK_Help = 0xff6a, /* Help */ - XK_Break = 0xff6b, - XK_Mode_switch = 0xff7e, /* Character set switch */ - XK_script_switch = 0xff7e, /* Alias for mode_switch */ - XK_Num_Lock = 0xff7f, - XK_KP_Space = 0xff80, /* Space */ - XK_KP_Tab = 0xff89, - XK_KP_Enter = 0xff8d, /* Enter */ - XK_KP_F1 = 0xff91, /* PF1, KP_A, ... */ - XK_KP_F2 = 0xff92, - XK_KP_F3 = 0xff93, - XK_KP_F4 = 0xff94, - XK_KP_Home = 0xff95, - XK_KP_Left = 0xff96, - XK_KP_Up = 0xff97, - XK_KP_Right = 0xff98, - XK_KP_Down = 0xff99, - XK_KP_Prior = 0xff9a, - XK_KP_Page_Up = 0xff9a, - XK_KP_Next = 0xff9b, - XK_KP_Page_Down = 0xff9b, - XK_KP_End = 0xff9c, - XK_KP_Begin = 0xff9d, - XK_KP_Insert = 0xff9e, - XK_KP_Delete = 0xff9f, - XK_KP_Equal = 0xffbd, /* Equals */ - XK_KP_Multiply = 0xffaa, - XK_KP_Add = 0xffab, - XK_KP_Separator = 0xffac, /* Separator, often comma */ - XK_KP_Subtract = 0xffad, - XK_KP_Decimal = 0xffae, - XK_KP_Divide = 0xffaf, - XK_KP_0 = 0xffb0, - XK_KP_1 = 0xffb1, - XK_KP_2 = 0xffb2, - XK_KP_3 = 0xffb3, - XK_KP_4 = 0xffb4, - XK_KP_5 = 0xffb5, - XK_KP_6 = 0xffb6, - XK_KP_7 = 0xffb7, - XK_KP_8 = 0xffb8, - XK_KP_9 = 0xffb9, - XK_F1 = 0xffbe, - XK_F2 = 0xffbf, - XK_F3 = 0xffc0, - XK_F4 = 0xffc1, - XK_F5 = 0xffc2, - XK_F6 = 0xffc3, - XK_F7 = 0xffc4, - XK_F8 = 0xffc5, - XK_F9 = 0xffc6, - XK_F10 = 0xffc7, - XK_F11 = 0xffc8, - XK_L1 = 0xffc8, - XK_F12 = 0xffc9, - XK_L2 = 0xffc9, - XK_F13 = 0xffca, - XK_L3 = 0xffca, - XK_F14 = 0xffcb, - XK_L4 = 0xffcb, - XK_F15 = 0xffcc, - XK_L5 = 0xffcc, - XK_F16 = 0xffcd, - XK_L6 = 0xffcd, - XK_F17 = 0xffce, - XK_L7 = 0xffce, - XK_F18 = 0xffcf, - XK_L8 = 0xffcf, - XK_F19 = 0xffd0, - XK_L9 = 0xffd0, - XK_F20 = 0xffd1, - XK_L10 = 0xffd1, - XK_F21 = 0xffd2, - XK_R1 = 0xffd2, - XK_F22 = 0xffd3, - XK_R2 = 0xffd3, - XK_F23 = 0xffd4, - XK_R3 = 0xffd4, - XK_F24 = 0xffd5, - XK_R4 = 0xffd5, - XK_F25 = 0xffd6, - XK_R5 = 0xffd6, - XK_F26 = 0xffd7, - XK_R6 = 0xffd7, - XK_F27 = 0xffd8, - XK_R7 = 0xffd8, - XK_F28 = 0xffd9, - XK_R8 = 0xffd9, - XK_F29 = 0xffda, - XK_R9 = 0xffda, - XK_F30 = 0xffdb, - XK_R10 = 0xffdb, - XK_F31 = 0xffdc, - XK_R11 = 0xffdc, - XK_F32 = 0xffdd, - XK_R12 = 0xffdd, - XK_F33 = 0xffde, - XK_R13 = 0xffde, - XK_F34 = 0xffdf, - XK_R14 = 0xffdf, - XK_F35 = 0xffe0, - XK_R15 = 0xffe0, - XK_Shift_L = 0xffe1, /* Left shift */ - XK_Shift_R = 0xffe2, /* Right shift */ - XK_Control_L = 0xffe3, /* Left control */ - XK_Control_R = 0xffe4, /* Right control */ - XK_Caps_Lock = 0xffe5, /* Caps lock */ - XK_Shift_Lock = 0xffe6, /* Shift lock */ - XK_Meta_L = 0xffe7, /* Left meta */ - XK_Meta_R = 0xffe8, /* Right meta */ - XK_Alt_L = 0xffe9, /* Left alt */ - XK_Alt_R = 0xffea, /* Right alt */ - XK_Super_L = 0xffeb, /* Left super */ - XK_Super_R = 0xffec, /* Right super */ - XK_Hyper_L = 0xffed, /* Left hyper */ - XK_Hyper_R = 0xffee, /* Right hyper */ - XK_ISO_Lock = 0xfe01, - XK_ISO_Level2_Latch = 0xfe02, - XK_ISO_Level3_Shift = 0xfe03, - XK_ISO_Level3_Latch = 0xfe04, - XK_ISO_Level3_Lock = 0xfe05, - XK_ISO_Group_Shift = 0xff7e, /* Alias for mode_switch */ - XK_ISO_Group_Latch = 0xfe06, - XK_ISO_Group_Lock = 0xfe07, - XK_ISO_Next_Group = 0xfe08, - XK_ISO_Next_Group_Lock = 0xfe09, - XK_ISO_Prev_Group = 0xfe0a, - XK_ISO_Prev_Group_Lock = 0xfe0b, - XK_ISO_First_Group = 0xfe0c, - XK_ISO_First_Group_Lock = 0xfe0d, - XK_ISO_Last_Group = 0xfe0e, - XK_ISO_Last_Group_Lock = 0xfe0f, - XK_ISO_Left_Tab = 0xfe20, - XK_ISO_Move_Line_Up = 0xfe21, - XK_ISO_Move_Line_Down = 0xfe22, - XK_ISO_Partial_Line_Up = 0xfe23, - XK_ISO_Partial_Line_Down = 0xfe24, - XK_ISO_Partial_Space_Left = 0xfe25, - XK_ISO_Partial_Space_Right = 0xfe26, - XK_ISO_Set_Margin_Left = 0xfe27, - XK_ISO_Set_Margin_Right = 0xfe28, - XK_ISO_Release_Margin_Left = 0xfe29, - XK_ISO_Release_Margin_Right = 0xfe2a, - XK_ISO_Release_Both_Margins = 0xfe2b, - XK_ISO_Fast_Cursor_Left = 0xfe2c, - XK_ISO_Fast_Cursor_Right = 0xfe2d, - XK_ISO_Fast_Cursor_Up = 0xfe2e, - XK_ISO_Fast_Cursor_Down = 0xfe2f, - XK_ISO_Continuous_Underline = 0xfe30, - XK_ISO_Discontinuous_Underline = 0xfe31, - XK_ISO_Emphasize = 0xfe32, - XK_ISO_Center_Object = 0xfe33, - XK_ISO_Enter = 0xfe34, - XK_dead_grave = 0xfe50, - XK_dead_acute = 0xfe51, - XK_dead_circumflex = 0xfe52, - XK_dead_tilde = 0xfe53, - XK_dead_macron = 0xfe54, - XK_dead_breve = 0xfe55, - XK_dead_abovedot = 0xfe56, - XK_dead_diaeresis = 0xfe57, - XK_dead_abovering = 0xfe58, - XK_dead_doubleacute = 0xfe59, - XK_dead_caron = 0xfe5a, - XK_dead_cedilla = 0xfe5b, - XK_dead_ogonek = 0xfe5c, - XK_dead_iota = 0xfe5d, - XK_dead_voiced_sound = 0xfe5e, - XK_dead_semivoiced_sound = 0xfe5f, - XK_dead_belowdot = 0xfe60, - XK_dead_hook = 0xfe61, - XK_dead_horn = 0xfe62, - XK_First_Virtual_Screen = 0xfed0, - XK_Prev_Virtual_Screen = 0xfed1, - XK_Next_Virtual_Screen = 0xfed2, - XK_Last_Virtual_Screen = 0xfed4, - XK_Terminate_Server = 0xfed5, - XK_AccessX_Enable = 0xfe70, - XK_AccessX_Feedback_Enable = 0xfe71, - XK_RepeatKeys_Enable = 0xfe72, - XK_SlowKeys_Enable = 0xfe73, - XK_BounceKeys_Enable = 0xfe74, - XK_StickyKeys_Enable = 0xfe75, - XK_MouseKeys_Enable = 0xfe76, - XK_MouseKeys_Accel_Enable = 0xfe77, - XK_Overlay1_Enable = 0xfe78, - XK_Overlay2_Enable = 0xfe79, - XK_AudibleBell_Enable = 0xfe7a, - XK_Pointer_Left = 0xfee0, - XK_Pointer_Right = 0xfee1, - XK_Pointer_Up = 0xfee2, - XK_Pointer_Down = 0xfee3, - XK_Pointer_UpLeft = 0xfee4, - XK_Pointer_UpRight = 0xfee5, - XK_Pointer_DownLeft = 0xfee6, - XK_Pointer_DownRight = 0xfee7, - XK_Pointer_Button_Dflt = 0xfee8, - XK_Pointer_Button1 = 0xfee9, - XK_Pointer_Button2 = 0xfeea, - XK_Pointer_Button3 = 0xfeeb, - XK_Pointer_Button4 = 0xfeec, - XK_Pointer_Button5 = 0xfeed, - XK_Pointer_DblClick_Dflt = 0xfeee, - XK_Pointer_DblClick1 = 0xfeef, - XK_Pointer_DblClick2 = 0xfef0, - XK_Pointer_DblClick3 = 0xfef1, - XK_Pointer_DblClick4 = 0xfef2, - XK_Pointer_DblClick5 = 0xfef3, - XK_Pointer_Drag_Dflt = 0xfef4, - XK_Pointer_Drag1 = 0xfef5, - XK_Pointer_Drag2 = 0xfef6, - XK_Pointer_Drag3 = 0xfef7, - XK_Pointer_Drag4 = 0xfef8, - XK_Pointer_Drag5 = 0xfefd, - XK_Pointer_EnableKeys = 0xfef9, - XK_Pointer_Accelerate = 0xfefa, - XK_Pointer_DfltBtnNext = 0xfefb, - XK_Pointer_DfltBtnPrev = 0xfefc, - XK_3270_Duplicate = 0xfd01, - XK_3270_FieldMark = 0xfd02, - XK_3270_Right2 = 0xfd03, - XK_3270_Left2 = 0xfd04, - XK_3270_BackTab = 0xfd05, - XK_3270_EraseEOF = 0xfd06, - XK_3270_EraseInput = 0xfd07, - XK_3270_Reset = 0xfd08, - XK_3270_Quit = 0xfd09, - XK_3270_PA1 = 0xfd0a, - XK_3270_PA2 = 0xfd0b, - XK_3270_PA3 = 0xfd0c, - XK_3270_Test = 0xfd0d, - XK_3270_Attn = 0xfd0e, - XK_3270_CursorBlink = 0xfd0f, - XK_3270_AltCursor = 0xfd10, - XK_3270_KeyClick = 0xfd11, - XK_3270_Jump = 0xfd12, - XK_3270_Ident = 0xfd13, - XK_3270_Rule = 0xfd14, - XK_3270_Copy = 0xfd15, - XK_3270_Play = 0xfd16, - XK_3270_Setup = 0xfd17, - XK_3270_Record = 0xfd18, - XK_3270_ChangeScreen = 0xfd19, - XK_3270_DeleteWord = 0xfd1a, - XK_3270_ExSelect = 0xfd1b, - XK_3270_CursorSelect = 0xfd1c, - XK_3270_PrintScreen = 0xfd1d, - XK_3270_Enter = 0xfd1e, - XK_space = 0x0020, /* U+0020 SPACE */ - XK_exclam = 0x0021, /* U+0021 EXCLAMATION MARK */ - XK_quotedbl = 0x0022, /* U+0022 QUOTATION MARK */ - XK_numbersign = 0x0023, /* U+0023 NUMBER SIGN */ - XK_dollar = 0x0024, /* U+0024 DOLLAR SIGN */ - XK_percent = 0x0025, /* U+0025 PERCENT SIGN */ - XK_ampersand = 0x0026, /* U+0026 AMPERSAND */ - XK_apostrophe = 0x0027, /* U+0027 APOSTROPHE */ - XK_quoteright = 0x0027, /* deprecated */ - XK_parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */ - XK_parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */ - XK_asterisk = 0x002a, /* U+002A ASTERISK */ - XK_plus = 0x002b, /* U+002B PLUS SIGN */ - XK_comma = 0x002c, /* U+002C COMMA */ - XK_minus = 0x002d, /* U+002D HYPHEN-MINUS */ - XK_period = 0x002e, /* U+002E FULL STOP */ - XK_slash = 0x002f, /* U+002F SOLIDUS */ - XK_0 = 0x0030, /* U+0030 DIGIT ZERO */ - XK_1 = 0x0031, /* U+0031 DIGIT ONE */ - XK_2 = 0x0032, /* U+0032 DIGIT TWO */ - XK_3 = 0x0033, /* U+0033 DIGIT THREE */ - XK_4 = 0x0034, /* U+0034 DIGIT FOUR */ - XK_5 = 0x0035, /* U+0035 DIGIT FIVE */ - XK_6 = 0x0036, /* U+0036 DIGIT SIX */ - XK_7 = 0x0037, /* U+0037 DIGIT SEVEN */ - XK_8 = 0x0038, /* U+0038 DIGIT EIGHT */ - XK_9 = 0x0039, /* U+0039 DIGIT NINE */ - XK_colon = 0x003a, /* U+003A COLON */ - XK_semicolon = 0x003b, /* U+003B SEMICOLON */ - XK_less = 0x003c, /* U+003C LESS-THAN SIGN */ - XK_equal = 0x003d, /* U+003D EQUALS SIGN */ - XK_greater = 0x003e, /* U+003E GREATER-THAN SIGN */ - XK_question = 0x003f, /* U+003F QUESTION MARK */ - XK_at = 0x0040, /* U+0040 COMMERCIAL AT */ - XK_A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */ - XK_B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */ - XK_C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */ - XK_D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */ - XK_E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */ - XK_F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */ - XK_G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */ - XK_H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */ - XK_I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */ - XK_J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */ - XK_K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */ - XK_L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */ - XK_M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */ - XK_N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */ - XK_O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */ - XK_P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */ - XK_Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */ - XK_R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */ - XK_S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */ - XK_T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */ - XK_U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */ - XK_V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */ - XK_W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */ - XK_X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */ - XK_Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */ - XK_Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */ - XK_bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */ - XK_backslash = 0x005c, /* U+005C REVERSE SOLIDUS */ - XK_bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */ - XK_asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */ - XK_underscore = 0x005f, /* U+005F LOW LINE */ - XK_grave = 0x0060, /* U+0060 GRAVE ACCENT */ - XK_quoteleft = 0x0060, /* deprecated */ - XK_a = 0x0061, /* U+0061 LATIN SMALL LETTER A */ - XK_b = 0x0062, /* U+0062 LATIN SMALL LETTER B */ - XK_c = 0x0063, /* U+0063 LATIN SMALL LETTER C */ - XK_d = 0x0064, /* U+0064 LATIN SMALL LETTER D */ - XK_e = 0x0065, /* U+0065 LATIN SMALL LETTER E */ - XK_f = 0x0066, /* U+0066 LATIN SMALL LETTER F */ - XK_g = 0x0067, /* U+0067 LATIN SMALL LETTER G */ - XK_h = 0x0068, /* U+0068 LATIN SMALL LETTER H */ - XK_i = 0x0069, /* U+0069 LATIN SMALL LETTER I */ - XK_j = 0x006a, /* U+006A LATIN SMALL LETTER J */ - XK_k = 0x006b, /* U+006B LATIN SMALL LETTER K */ - XK_l = 0x006c, /* U+006C LATIN SMALL LETTER L */ - XK_m = 0x006d, /* U+006D LATIN SMALL LETTER M */ - XK_n = 0x006e, /* U+006E LATIN SMALL LETTER N */ - XK_o = 0x006f, /* U+006F LATIN SMALL LETTER O */ - XK_p = 0x0070, /* U+0070 LATIN SMALL LETTER P */ - XK_q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */ - XK_r = 0x0072, /* U+0072 LATIN SMALL LETTER R */ - XK_s = 0x0073, /* U+0073 LATIN SMALL LETTER S */ - XK_t = 0x0074, /* U+0074 LATIN SMALL LETTER T */ - XK_u = 0x0075, /* U+0075 LATIN SMALL LETTER U */ - XK_v = 0x0076, /* U+0076 LATIN SMALL LETTER V */ - XK_w = 0x0077, /* U+0077 LATIN SMALL LETTER W */ - XK_x = 0x0078, /* U+0078 LATIN SMALL LETTER X */ - XK_y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */ - XK_z = 0x007a, /* U+007A LATIN SMALL LETTER Z */ - XK_braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */ - XK_bar = 0x007c, /* U+007C VERTICAL LINE */ - XK_braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */ - XK_asciitilde = 0x007e, /* U+007E TILDE */ - XK_nobreakspace = 0x00a0, /* U+00A0 NO-BREAK SPACE */ - XK_exclamdown = 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */ - XK_cent = 0x00a2, /* U+00A2 CENT SIGN */ - XK_sterling = 0x00a3, /* U+00A3 POUND SIGN */ - XK_currency = 0x00a4, /* U+00A4 CURRENCY SIGN */ - XK_yen = 0x00a5, /* U+00A5 YEN SIGN */ - XK_brokenbar = 0x00a6, /* U+00A6 BROKEN BAR */ - XK_section = 0x00a7, /* U+00A7 SECTION SIGN */ - XK_diaeresis = 0x00a8, /* U+00A8 DIAERESIS */ - XK_copyright = 0x00a9, /* U+00A9 COPYRIGHT SIGN */ - XK_ordfeminine = 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */ - XK_guillemotleft = 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */ - XK_notsign = 0x00ac, /* U+00AC NOT SIGN */ - XK_hyphen = 0x00ad, /* U+00AD SOFT HYPHEN */ - XK_registered = 0x00ae, /* U+00AE REGISTERED SIGN */ - XK_macron = 0x00af, /* U+00AF MACRON */ - XK_degree = 0x00b0, /* U+00B0 DEGREE SIGN */ - XK_plusminus = 0x00b1, /* U+00B1 PLUS-MINUS SIGN */ - XK_twosuperior = 0x00b2, /* U+00B2 SUPERSCRIPT TWO */ - XK_threesuperior = 0x00b3, /* U+00B3 SUPERSCRIPT THREE */ - XK_acute = 0x00b4, /* U+00B4 ACUTE ACCENT */ - XK_mu = 0x00b5, /* U+00B5 MICRO SIGN */ - XK_paragraph = 0x00b6, /* U+00B6 PILCROW SIGN */ - XK_periodcentered = 0x00b7, /* U+00B7 MIDDLE DOT */ - XK_cedilla = 0x00b8, /* U+00B8 CEDILLA */ - XK_onesuperior = 0x00b9, /* U+00B9 SUPERSCRIPT ONE */ - XK_masculine = 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */ - XK_guillemotright = 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */ - XK_onequarter = 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */ - XK_onehalf = 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */ - XK_threequarters = 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */ - XK_questiondown = 0x00bf, /* U+00BF INVERTED QUESTION MARK */ - XK_Agrave = 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */ - XK_Aacute = 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */ - XK_Acircumflex = 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */ - XK_Atilde = 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */ - XK_Adiaeresis = 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */ - XK_Aring = 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */ - XK_AE = 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */ - XK_Ccedilla = 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */ - XK_Egrave = 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */ - XK_Eacute = 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */ - XK_Ecircumflex = 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */ - XK_Ediaeresis = 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */ - XK_Igrave = 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */ - XK_Iacute = 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */ - XK_Icircumflex = 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */ - XK_Idiaeresis = 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */ - XK_ETH = 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */ - XK_Eth = 0x00d0, /* deprecated */ - XK_Ntilde = 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */ - XK_Ograve = 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */ - XK_Oacute = 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */ - XK_Ocircumflex = 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */ - XK_Otilde = 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */ - XK_Odiaeresis = 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */ - XK_multiply = 0x00d7, /* U+00D7 MULTIPLICATION SIGN */ - XK_Oslash = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */ - XK_Ooblique = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */ - XK_Ugrave = 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */ - XK_Uacute = 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */ - XK_Ucircumflex = 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */ - XK_Udiaeresis = 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */ - XK_Yacute = 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */ - XK_THORN = 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */ - XK_Thorn = 0x00de, /* deprecated */ - XK_ssharp = 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */ - XK_agrave = 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */ - XK_aacute = 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */ - XK_acircumflex = 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */ - XK_atilde = 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */ - XK_adiaeresis = 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */ - XK_aring = 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */ - XK_ae = 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */ - XK_ccedilla = 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */ - XK_egrave = 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */ - XK_eacute = 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */ - XK_ecircumflex = 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */ - XK_ediaeresis = 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */ - XK_igrave = 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */ - XK_iacute = 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */ - XK_icircumflex = 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */ - XK_idiaeresis = 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */ - XK_eth = 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */ - XK_ntilde = 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */ - XK_ograve = 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */ - XK_oacute = 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */ - XK_ocircumflex = 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */ - XK_otilde = 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */ - XK_odiaeresis = 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */ - XK_division = 0x00f7, /* U+00F7 DIVISION SIGN */ - XK_oslash = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */ - XK_ooblique = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */ - XK_ugrave = 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */ - XK_uacute = 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */ - XK_ucircumflex = 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */ - XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */ - XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */ - XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ - XK_ydiaeresis = 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ - XK_Aogonek = 0x01a1, /* U+0104 LATIN CAPITAL LETTER A WITH OGONEK */ - XK_breve = 0x01a2, /* U+02D8 BREVE */ - XK_Lstroke = 0x01a3, /* U+0141 LATIN CAPITAL LETTER L WITH STROKE */ - XK_Lcaron = 0x01a5, /* U+013D LATIN CAPITAL LETTER L WITH CARON */ - XK_Sacute = 0x01a6, /* U+015A LATIN CAPITAL LETTER S WITH ACUTE */ - XK_Scaron = 0x01a9, /* U+0160 LATIN CAPITAL LETTER S WITH CARON */ - XK_Scedilla = 0x01aa, /* U+015E LATIN CAPITAL LETTER S WITH CEDILLA */ - XK_Tcaron = 0x01ab, /* U+0164 LATIN CAPITAL LETTER T WITH CARON */ - XK_Zacute = 0x01ac, /* U+0179 LATIN CAPITAL LETTER Z WITH ACUTE */ - XK_Zcaron = 0x01ae, /* U+017D LATIN CAPITAL LETTER Z WITH CARON */ - XK_Zabovedot = 0x01af, /* U+017B LATIN CAPITAL LETTER Z WITH DOT ABOVE */ - XK_aogonek = 0x01b1, /* U+0105 LATIN SMALL LETTER A WITH OGONEK */ - XK_ogonek = 0x01b2, /* U+02DB OGONEK */ - XK_lstroke = 0x01b3, /* U+0142 LATIN SMALL LETTER L WITH STROKE */ - XK_lcaron = 0x01b5, /* U+013E LATIN SMALL LETTER L WITH CARON */ - XK_sacute = 0x01b6, /* U+015B LATIN SMALL LETTER S WITH ACUTE */ - XK_caron = 0x01b7, /* U+02C7 CARON */ - XK_scaron = 0x01b9, /* U+0161 LATIN SMALL LETTER S WITH CARON */ - XK_scedilla = 0x01ba, /* U+015F LATIN SMALL LETTER S WITH CEDILLA */ - XK_tcaron = 0x01bb, /* U+0165 LATIN SMALL LETTER T WITH CARON */ - XK_zacute = 0x01bc, /* U+017A LATIN SMALL LETTER Z WITH ACUTE */ - XK_doubleacute = 0x01bd, /* U+02DD DOUBLE ACUTE ACCENT */ - XK_zcaron = 0x01be, /* U+017E LATIN SMALL LETTER Z WITH CARON */ - XK_zabovedot = 0x01bf, /* U+017C LATIN SMALL LETTER Z WITH DOT ABOVE */ - XK_Racute = 0x01c0, /* U+0154 LATIN CAPITAL LETTER R WITH ACUTE */ - XK_Abreve = 0x01c3, /* U+0102 LATIN CAPITAL LETTER A WITH BREVE */ - XK_Lacute = 0x01c5, /* U+0139 LATIN CAPITAL LETTER L WITH ACUTE */ - XK_Cacute = 0x01c6, /* U+0106 LATIN CAPITAL LETTER C WITH ACUTE */ - XK_Ccaron = 0x01c8, /* U+010C LATIN CAPITAL LETTER C WITH CARON */ - XK_Eogonek = 0x01ca, /* U+0118 LATIN CAPITAL LETTER E WITH OGONEK */ - XK_Ecaron = 0x01cc, /* U+011A LATIN CAPITAL LETTER E WITH CARON */ - XK_Dcaron = 0x01cf, /* U+010E LATIN CAPITAL LETTER D WITH CARON */ - XK_Dstroke = 0x01d0, /* U+0110 LATIN CAPITAL LETTER D WITH STROKE */ - XK_Nacute = 0x01d1, /* U+0143 LATIN CAPITAL LETTER N WITH ACUTE */ - XK_Ncaron = 0x01d2, /* U+0147 LATIN CAPITAL LETTER N WITH CARON */ - XK_Odoubleacute = 0x01d5, /* U+0150 LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ - XK_Rcaron = 0x01d8, /* U+0158 LATIN CAPITAL LETTER R WITH CARON */ - XK_Uring = 0x01d9, /* U+016E LATIN CAPITAL LETTER U WITH RING ABOVE */ - XK_Udoubleacute = 0x01db, /* U+0170 LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ - XK_Tcedilla = 0x01de, /* U+0162 LATIN CAPITAL LETTER T WITH CEDILLA */ - XK_racute = 0x01e0, /* U+0155 LATIN SMALL LETTER R WITH ACUTE */ - XK_abreve = 0x01e3, /* U+0103 LATIN SMALL LETTER A WITH BREVE */ - XK_lacute = 0x01e5, /* U+013A LATIN SMALL LETTER L WITH ACUTE */ - XK_cacute = 0x01e6, /* U+0107 LATIN SMALL LETTER C WITH ACUTE */ - XK_ccaron = 0x01e8, /* U+010D LATIN SMALL LETTER C WITH CARON */ - XK_eogonek = 0x01ea, /* U+0119 LATIN SMALL LETTER E WITH OGONEK */ - XK_ecaron = 0x01ec, /* U+011B LATIN SMALL LETTER E WITH CARON */ - XK_dcaron = 0x01ef, /* U+010F LATIN SMALL LETTER D WITH CARON */ - XK_dstroke = 0x01f0, /* U+0111 LATIN SMALL LETTER D WITH STROKE */ - XK_nacute = 0x01f1, /* U+0144 LATIN SMALL LETTER N WITH ACUTE */ - XK_ncaron = 0x01f2, /* U+0148 LATIN SMALL LETTER N WITH CARON */ - XK_odoubleacute = 0x01f5, /* U+0151 LATIN SMALL LETTER O WITH DOUBLE ACUTE */ - XK_udoubleacute = 0x01fb, /* U+0171 LATIN SMALL LETTER U WITH DOUBLE ACUTE */ - XK_rcaron = 0x01f8, /* U+0159 LATIN SMALL LETTER R WITH CARON */ - XK_uring = 0x01f9, /* U+016F LATIN SMALL LETTER U WITH RING ABOVE */ - XK_tcedilla = 0x01fe, /* U+0163 LATIN SMALL LETTER T WITH CEDILLA */ - XK_abovedot = 0x01ff, /* U+02D9 DOT ABOVE */ - XK_Hstroke = 0x02a1, /* U+0126 LATIN CAPITAL LETTER H WITH STROKE */ - XK_Hcircumflex = 0x02a6, /* U+0124 LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ - XK_Iabovedot = 0x02a9, /* U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE */ - XK_Gbreve = 0x02ab, /* U+011E LATIN CAPITAL LETTER G WITH BREVE */ - XK_Jcircumflex = 0x02ac, /* U+0134 LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ - XK_hstroke = 0x02b1, /* U+0127 LATIN SMALL LETTER H WITH STROKE */ - XK_hcircumflex = 0x02b6, /* U+0125 LATIN SMALL LETTER H WITH CIRCUMFLEX */ - XK_idotless = 0x02b9, /* U+0131 LATIN SMALL LETTER DOTLESS I */ - XK_gbreve = 0x02bb, /* U+011F LATIN SMALL LETTER G WITH BREVE */ - XK_jcircumflex = 0x02bc, /* U+0135 LATIN SMALL LETTER J WITH CIRCUMFLEX */ - XK_Cabovedot = 0x02c5, /* U+010A LATIN CAPITAL LETTER C WITH DOT ABOVE */ - XK_Ccircumflex = 0x02c6, /* U+0108 LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ - XK_Gabovedot = 0x02d5, /* U+0120 LATIN CAPITAL LETTER G WITH DOT ABOVE */ - XK_Gcircumflex = 0x02d8, /* U+011C LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ - XK_Ubreve = 0x02dd, /* U+016C LATIN CAPITAL LETTER U WITH BREVE */ - XK_Scircumflex = 0x02de, /* U+015C LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ - XK_cabovedot = 0x02e5, /* U+010B LATIN SMALL LETTER C WITH DOT ABOVE */ - XK_ccircumflex = 0x02e6, /* U+0109 LATIN SMALL LETTER C WITH CIRCUMFLEX */ - XK_gabovedot = 0x02f5, /* U+0121 LATIN SMALL LETTER G WITH DOT ABOVE */ - XK_gcircumflex = 0x02f8, /* U+011D LATIN SMALL LETTER G WITH CIRCUMFLEX */ - XK_ubreve = 0x02fd, /* U+016D LATIN SMALL LETTER U WITH BREVE */ - XK_scircumflex = 0x02fe, /* U+015D LATIN SMALL LETTER S WITH CIRCUMFLEX */ - XK_kra = 0x03a2, /* U+0138 LATIN SMALL LETTER KRA */ - XK_kappa = 0x03a2, /* deprecated */ - XK_Rcedilla = 0x03a3, /* U+0156 LATIN CAPITAL LETTER R WITH CEDILLA */ - XK_Itilde = 0x03a5, /* U+0128 LATIN CAPITAL LETTER I WITH TILDE */ - XK_Lcedilla = 0x03a6, /* U+013B LATIN CAPITAL LETTER L WITH CEDILLA */ - XK_Emacron = 0x03aa, /* U+0112 LATIN CAPITAL LETTER E WITH MACRON */ - XK_Gcedilla = 0x03ab, /* U+0122 LATIN CAPITAL LETTER G WITH CEDILLA */ - XK_Tslash = 0x03ac, /* U+0166 LATIN CAPITAL LETTER T WITH STROKE */ - XK_rcedilla = 0x03b3, /* U+0157 LATIN SMALL LETTER R WITH CEDILLA */ - XK_itilde = 0x03b5, /* U+0129 LATIN SMALL LETTER I WITH TILDE */ - XK_lcedilla = 0x03b6, /* U+013C LATIN SMALL LETTER L WITH CEDILLA */ - XK_emacron = 0x03ba, /* U+0113 LATIN SMALL LETTER E WITH MACRON */ - XK_gcedilla = 0x03bb, /* U+0123 LATIN SMALL LETTER G WITH CEDILLA */ - XK_tslash = 0x03bc, /* U+0167 LATIN SMALL LETTER T WITH STROKE */ - XK_ENG = 0x03bd, /* U+014A LATIN CAPITAL LETTER ENG */ - XK_eng = 0x03bf, /* U+014B LATIN SMALL LETTER ENG */ - XK_Amacron = 0x03c0, /* U+0100 LATIN CAPITAL LETTER A WITH MACRON */ - XK_Iogonek = 0x03c7, /* U+012E LATIN CAPITAL LETTER I WITH OGONEK */ - XK_Eabovedot = 0x03cc, /* U+0116 LATIN CAPITAL LETTER E WITH DOT ABOVE */ - XK_Imacron = 0x03cf, /* U+012A LATIN CAPITAL LETTER I WITH MACRON */ - XK_Ncedilla = 0x03d1, /* U+0145 LATIN CAPITAL LETTER N WITH CEDILLA */ - XK_Omacron = 0x03d2, /* U+014C LATIN CAPITAL LETTER O WITH MACRON */ - XK_Kcedilla = 0x03d3, /* U+0136 LATIN CAPITAL LETTER K WITH CEDILLA */ - XK_Uogonek = 0x03d9, /* U+0172 LATIN CAPITAL LETTER U WITH OGONEK */ - XK_Utilde = 0x03dd, /* U+0168 LATIN CAPITAL LETTER U WITH TILDE */ - XK_Umacron = 0x03de, /* U+016A LATIN CAPITAL LETTER U WITH MACRON */ - XK_amacron = 0x03e0, /* U+0101 LATIN SMALL LETTER A WITH MACRON */ - XK_iogonek = 0x03e7, /* U+012F LATIN SMALL LETTER I WITH OGONEK */ - XK_eabovedot = 0x03ec, /* U+0117 LATIN SMALL LETTER E WITH DOT ABOVE */ - XK_imacron = 0x03ef, /* U+012B LATIN SMALL LETTER I WITH MACRON */ - XK_ncedilla = 0x03f1, /* U+0146 LATIN SMALL LETTER N WITH CEDILLA */ - XK_omacron = 0x03f2, /* U+014D LATIN SMALL LETTER O WITH MACRON */ - XK_kcedilla = 0x03f3, /* U+0137 LATIN SMALL LETTER K WITH CEDILLA */ - XK_uogonek = 0x03f9, /* U+0173 LATIN SMALL LETTER U WITH OGONEK */ - XK_utilde = 0x03fd, /* U+0169 LATIN SMALL LETTER U WITH TILDE */ - XK_umacron = 0x03fe, /* U+016B LATIN SMALL LETTER U WITH MACRON */ - XK_Babovedot = 0x1001e02, /* U+1E02 LATIN CAPITAL LETTER B WITH DOT ABOVE */ - XK_babovedot = 0x1001e03, /* U+1E03 LATIN SMALL LETTER B WITH DOT ABOVE */ - XK_Dabovedot = 0x1001e0a, /* U+1E0A LATIN CAPITAL LETTER D WITH DOT ABOVE */ - XK_Wgrave = 0x1001e80, /* U+1E80 LATIN CAPITAL LETTER W WITH GRAVE */ - XK_Wacute = 0x1001e82, /* U+1E82 LATIN CAPITAL LETTER W WITH ACUTE */ - XK_dabovedot = 0x1001e0b, /* U+1E0B LATIN SMALL LETTER D WITH DOT ABOVE */ - XK_Ygrave = 0x1001ef2, /* U+1EF2 LATIN CAPITAL LETTER Y WITH GRAVE */ - XK_Fabovedot = 0x1001e1e, /* U+1E1E LATIN CAPITAL LETTER F WITH DOT ABOVE */ - XK_fabovedot = 0x1001e1f, /* U+1E1F LATIN SMALL LETTER F WITH DOT ABOVE */ - XK_Mabovedot = 0x1001e40, /* U+1E40 LATIN CAPITAL LETTER M WITH DOT ABOVE */ - XK_mabovedot = 0x1001e41, /* U+1E41 LATIN SMALL LETTER M WITH DOT ABOVE */ - XK_Pabovedot = 0x1001e56, /* U+1E56 LATIN CAPITAL LETTER P WITH DOT ABOVE */ - XK_wgrave = 0x1001e81, /* U+1E81 LATIN SMALL LETTER W WITH GRAVE */ - XK_pabovedot = 0x1001e57, /* U+1E57 LATIN SMALL LETTER P WITH DOT ABOVE */ - XK_wacute = 0x1001e83, /* U+1E83 LATIN SMALL LETTER W WITH ACUTE */ - XK_Sabovedot = 0x1001e60, /* U+1E60 LATIN CAPITAL LETTER S WITH DOT ABOVE */ - XK_ygrave = 0x1001ef3, /* U+1EF3 LATIN SMALL LETTER Y WITH GRAVE */ - XK_Wdiaeresis = 0x1001e84, /* U+1E84 LATIN CAPITAL LETTER W WITH DIAERESIS */ - XK_wdiaeresis = 0x1001e85, /* U+1E85 LATIN SMALL LETTER W WITH DIAERESIS */ - XK_sabovedot = 0x1001e61, /* U+1E61 LATIN SMALL LETTER S WITH DOT ABOVE */ - XK_Wcircumflex = 0x1000174, /* U+0174 LATIN CAPITAL LETTER W WITH CIRCUMFLEX */ - XK_Tabovedot = 0x1001e6a, /* U+1E6A LATIN CAPITAL LETTER T WITH DOT ABOVE */ - XK_Ycircumflex = 0x1000176, /* U+0176 LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */ - XK_wcircumflex = 0x1000175, /* U+0175 LATIN SMALL LETTER W WITH CIRCUMFLEX */ - XK_tabovedot = 0x1001e6b, /* U+1E6B LATIN SMALL LETTER T WITH DOT ABOVE */ - XK_ycircumflex = 0x1000177, /* U+0177 LATIN SMALL LETTER Y WITH CIRCUMFLEX */ - XK_OE = 0x13bc, /* U+0152 LATIN CAPITAL LIGATURE OE */ - XK_oe = 0x13bd, /* U+0153 LATIN SMALL LIGATURE OE */ - XK_Ydiaeresis = 0x13be, /* U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS */ - XK_overline = 0x047e, /* U+203E OVERLINE */ - XK_kana_fullstop = 0x04a1, /* U+3002 IDEOGRAPHIC FULL STOP */ - XK_kana_openingbracket = 0x04a2, /* U+300C LEFT CORNER BRACKET */ - XK_kana_closingbracket = 0x04a3, /* U+300D RIGHT CORNER BRACKET */ - XK_kana_comma = 0x04a4, /* U+3001 IDEOGRAPHIC COMMA */ - XK_kana_conjunctive = 0x04a5, /* U+30FB KATAKANA MIDDLE DOT */ - XK_kana_middledot = 0x04a5, /* deprecated */ - XK_kana_WO = 0x04a6, /* U+30F2 KATAKANA LETTER WO */ - XK_kana_a = 0x04a7, /* U+30A1 KATAKANA LETTER SMALL A */ - XK_kana_i = 0x04a8, /* U+30A3 KATAKANA LETTER SMALL I */ - XK_kana_u = 0x04a9, /* U+30A5 KATAKANA LETTER SMALL U */ - XK_kana_e = 0x04aa, /* U+30A7 KATAKANA LETTER SMALL E */ - XK_kana_o = 0x04ab, /* U+30A9 KATAKANA LETTER SMALL O */ - XK_kana_ya = 0x04ac, /* U+30E3 KATAKANA LETTER SMALL YA */ - XK_kana_yu = 0x04ad, /* U+30E5 KATAKANA LETTER SMALL YU */ - XK_kana_yo = 0x04ae, /* U+30E7 KATAKANA LETTER SMALL YO */ - XK_kana_tsu = 0x04af, /* U+30C3 KATAKANA LETTER SMALL TU */ - XK_kana_tu = 0x04af, /* deprecated */ - XK_prolongedsound = 0x04b0, /* U+30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK */ - XK_kana_A = 0x04b1, /* U+30A2 KATAKANA LETTER A */ - XK_kana_I = 0x04b2, /* U+30A4 KATAKANA LETTER I */ - XK_kana_U = 0x04b3, /* U+30A6 KATAKANA LETTER U */ - XK_kana_E = 0x04b4, /* U+30A8 KATAKANA LETTER E */ - XK_kana_O = 0x04b5, /* U+30AA KATAKANA LETTER O */ - XK_kana_KA = 0x04b6, /* U+30AB KATAKANA LETTER KA */ - XK_kana_KI = 0x04b7, /* U+30AD KATAKANA LETTER KI */ - XK_kana_KU = 0x04b8, /* U+30AF KATAKANA LETTER KU */ - XK_kana_KE = 0x04b9, /* U+30B1 KATAKANA LETTER KE */ - XK_kana_KO = 0x04ba, /* U+30B3 KATAKANA LETTER KO */ - XK_kana_SA = 0x04bb, /* U+30B5 KATAKANA LETTER SA */ - XK_kana_SHI = 0x04bc, /* U+30B7 KATAKANA LETTER SI */ - XK_kana_SU = 0x04bd, /* U+30B9 KATAKANA LETTER SU */ - XK_kana_SE = 0x04be, /* U+30BB KATAKANA LETTER SE */ - XK_kana_SO = 0x04bf, /* U+30BD KATAKANA LETTER SO */ - XK_kana_TA = 0x04c0, /* U+30BF KATAKANA LETTER TA */ - XK_kana_CHI = 0x04c1, /* U+30C1 KATAKANA LETTER TI */ - XK_kana_TI = 0x04c1, /* deprecated */ - XK_kana_TSU = 0x04c2, /* U+30C4 KATAKANA LETTER TU */ - XK_kana_TU = 0x04c2, /* deprecated */ - XK_kana_TE = 0x04c3, /* U+30C6 KATAKANA LETTER TE */ - XK_kana_TO = 0x04c4, /* U+30C8 KATAKANA LETTER TO */ - XK_kana_NA = 0x04c5, /* U+30CA KATAKANA LETTER NA */ - XK_kana_NI = 0x04c6, /* U+30CB KATAKANA LETTER NI */ - XK_kana_NU = 0x04c7, /* U+30CC KATAKANA LETTER NU */ - XK_kana_NE = 0x04c8, /* U+30CD KATAKANA LETTER NE */ - XK_kana_NO = 0x04c9, /* U+30CE KATAKANA LETTER NO */ - XK_kana_HA = 0x04ca, /* U+30CF KATAKANA LETTER HA */ - XK_kana_HI = 0x04cb, /* U+30D2 KATAKANA LETTER HI */ - XK_kana_FU = 0x04cc, /* U+30D5 KATAKANA LETTER HU */ - XK_kana_HU = 0x04cc, /* deprecated */ - XK_kana_HE = 0x04cd, /* U+30D8 KATAKANA LETTER HE */ - XK_kana_HO = 0x04ce, /* U+30DB KATAKANA LETTER HO */ - XK_kana_MA = 0x04cf, /* U+30DE KATAKANA LETTER MA */ - XK_kana_MI = 0x04d0, /* U+30DF KATAKANA LETTER MI */ - XK_kana_MU = 0x04d1, /* U+30E0 KATAKANA LETTER MU */ - XK_kana_ME = 0x04d2, /* U+30E1 KATAKANA LETTER ME */ - XK_kana_MO = 0x04d3, /* U+30E2 KATAKANA LETTER MO */ - XK_kana_YA = 0x04d4, /* U+30E4 KATAKANA LETTER YA */ - XK_kana_YU = 0x04d5, /* U+30E6 KATAKANA LETTER YU */ - XK_kana_YO = 0x04d6, /* U+30E8 KATAKANA LETTER YO */ - XK_kana_RA = 0x04d7, /* U+30E9 KATAKANA LETTER RA */ - XK_kana_RI = 0x04d8, /* U+30EA KATAKANA LETTER RI */ - XK_kana_RU = 0x04d9, /* U+30EB KATAKANA LETTER RU */ - XK_kana_RE = 0x04da, /* U+30EC KATAKANA LETTER RE */ - XK_kana_RO = 0x04db, /* U+30ED KATAKANA LETTER RO */ - XK_kana_WA = 0x04dc, /* U+30EF KATAKANA LETTER WA */ - XK_kana_N = 0x04dd, /* U+30F3 KATAKANA LETTER N */ - XK_voicedsound = 0x04de, /* U+309B KATAKANA-HIRAGANA VOICED SOUND MARK */ - XK_semivoicedsound = 0x04df, /* U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ - XK_kana_switch = 0xff7e, /* Alias for mode_switch */ - XK_Farsi_0 = 0x10006f0, /* U+06F0 EXTENDED ARABIC-INDIC DIGIT ZERO */ - XK_Farsi_1 = 0x10006f1, /* U+06F1 EXTENDED ARABIC-INDIC DIGIT ONE */ - XK_Farsi_2 = 0x10006f2, /* U+06F2 EXTENDED ARABIC-INDIC DIGIT TWO */ - XK_Farsi_3 = 0x10006f3, /* U+06F3 EXTENDED ARABIC-INDIC DIGIT THREE */ - XK_Farsi_4 = 0x10006f4, /* U+06F4 EXTENDED ARABIC-INDIC DIGIT FOUR */ - XK_Farsi_5 = 0x10006f5, /* U+06F5 EXTENDED ARABIC-INDIC DIGIT FIVE */ - XK_Farsi_6 = 0x10006f6, /* U+06F6 EXTENDED ARABIC-INDIC DIGIT SIX */ - XK_Farsi_7 = 0x10006f7, /* U+06F7 EXTENDED ARABIC-INDIC DIGIT SEVEN */ - XK_Farsi_8 = 0x10006f8, /* U+06F8 EXTENDED ARABIC-INDIC DIGIT EIGHT */ - XK_Farsi_9 = 0x10006f9, /* U+06F9 EXTENDED ARABIC-INDIC DIGIT NINE */ - XK_Arabic_percent = 0x100066a, /* U+066A ARABIC PERCENT SIGN */ - XK_Arabic_superscript_alef = 0x1000670, /* U+0670 ARABIC LETTER SUPERSCRIPT ALEF */ - XK_Arabic_tteh = 0x1000679, /* U+0679 ARABIC LETTER TTEH */ - XK_Arabic_peh = 0x100067e, /* U+067E ARABIC LETTER PEH */ - XK_Arabic_tcheh = 0x1000686, /* U+0686 ARABIC LETTER TCHEH */ - XK_Arabic_ddal = 0x1000688, /* U+0688 ARABIC LETTER DDAL */ - XK_Arabic_rreh = 0x1000691, /* U+0691 ARABIC LETTER RREH */ - XK_Arabic_comma = 0x05ac, /* U+060C ARABIC COMMA */ - XK_Arabic_fullstop = 0x10006d4, /* U+06D4 ARABIC FULL STOP */ - XK_Arabic_0 = 0x1000660, /* U+0660 ARABIC-INDIC DIGIT ZERO */ - XK_Arabic_1 = 0x1000661, /* U+0661 ARABIC-INDIC DIGIT ONE */ - XK_Arabic_2 = 0x1000662, /* U+0662 ARABIC-INDIC DIGIT TWO */ - XK_Arabic_3 = 0x1000663, /* U+0663 ARABIC-INDIC DIGIT THREE */ - XK_Arabic_4 = 0x1000664, /* U+0664 ARABIC-INDIC DIGIT FOUR */ - XK_Arabic_5 = 0x1000665, /* U+0665 ARABIC-INDIC DIGIT FIVE */ - XK_Arabic_6 = 0x1000666, /* U+0666 ARABIC-INDIC DIGIT SIX */ - XK_Arabic_7 = 0x1000667, /* U+0667 ARABIC-INDIC DIGIT SEVEN */ - XK_Arabic_8 = 0x1000668, /* U+0668 ARABIC-INDIC DIGIT EIGHT */ - XK_Arabic_9 = 0x1000669, /* U+0669 ARABIC-INDIC DIGIT NINE */ - XK_Arabic_semicolon = 0x05bb, /* U+061B ARABIC SEMICOLON */ - XK_Arabic_question_mark = 0x05bf, /* U+061F ARABIC QUESTION MARK */ - XK_Arabic_hamza = 0x05c1, /* U+0621 ARABIC LETTER HAMZA */ - XK_Arabic_maddaonalef = 0x05c2, /* U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE */ - XK_Arabic_hamzaonalef = 0x05c3, /* U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE */ - XK_Arabic_hamzaonwaw = 0x05c4, /* U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE */ - XK_Arabic_hamzaunderalef = 0x05c5, /* U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW */ - XK_Arabic_hamzaonyeh = 0x05c6, /* U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE */ - XK_Arabic_alef = 0x05c7, /* U+0627 ARABIC LETTER ALEF */ - XK_Arabic_beh = 0x05c8, /* U+0628 ARABIC LETTER BEH */ - XK_Arabic_tehmarbuta = 0x05c9, /* U+0629 ARABIC LETTER TEH MARBUTA */ - XK_Arabic_teh = 0x05ca, /* U+062A ARABIC LETTER TEH */ - XK_Arabic_theh = 0x05cb, /* U+062B ARABIC LETTER THEH */ - XK_Arabic_jeem = 0x05cc, /* U+062C ARABIC LETTER JEEM */ - XK_Arabic_hah = 0x05cd, /* U+062D ARABIC LETTER HAH */ - XK_Arabic_khah = 0x05ce, /* U+062E ARABIC LETTER KHAH */ - XK_Arabic_dal = 0x05cf, /* U+062F ARABIC LETTER DAL */ - XK_Arabic_thal = 0x05d0, /* U+0630 ARABIC LETTER THAL */ - XK_Arabic_ra = 0x05d1, /* U+0631 ARABIC LETTER REH */ - XK_Arabic_zain = 0x05d2, /* U+0632 ARABIC LETTER ZAIN */ - XK_Arabic_seen = 0x05d3, /* U+0633 ARABIC LETTER SEEN */ - XK_Arabic_sheen = 0x05d4, /* U+0634 ARABIC LETTER SHEEN */ - XK_Arabic_sad = 0x05d5, /* U+0635 ARABIC LETTER SAD */ - XK_Arabic_dad = 0x05d6, /* U+0636 ARABIC LETTER DAD */ - XK_Arabic_tah = 0x05d7, /* U+0637 ARABIC LETTER TAH */ - XK_Arabic_zah = 0x05d8, /* U+0638 ARABIC LETTER ZAH */ - XK_Arabic_ain = 0x05d9, /* U+0639 ARABIC LETTER AIN */ - XK_Arabic_ghain = 0x05da, /* U+063A ARABIC LETTER GHAIN */ - XK_Arabic_tatweel = 0x05e0, /* U+0640 ARABIC TATWEEL */ - XK_Arabic_feh = 0x05e1, /* U+0641 ARABIC LETTER FEH */ - XK_Arabic_qaf = 0x05e2, /* U+0642 ARABIC LETTER QAF */ - XK_Arabic_kaf = 0x05e3, /* U+0643 ARABIC LETTER KAF */ - XK_Arabic_lam = 0x05e4, /* U+0644 ARABIC LETTER LAM */ - XK_Arabic_meem = 0x05e5, /* U+0645 ARABIC LETTER MEEM */ - XK_Arabic_noon = 0x05e6, /* U+0646 ARABIC LETTER NOON */ - XK_Arabic_ha = 0x05e7, /* U+0647 ARABIC LETTER HEH */ - XK_Arabic_heh = 0x05e7, /* deprecated */ - XK_Arabic_waw = 0x05e8, /* U+0648 ARABIC LETTER WAW */ - XK_Arabic_alefmaksura = 0x05e9, /* U+0649 ARABIC LETTER ALEF MAKSURA */ - XK_Arabic_yeh = 0x05ea, /* U+064A ARABIC LETTER YEH */ - XK_Arabic_fathatan = 0x05eb, /* U+064B ARABIC FATHATAN */ - XK_Arabic_dammatan = 0x05ec, /* U+064C ARABIC DAMMATAN */ - XK_Arabic_kasratan = 0x05ed, /* U+064D ARABIC KASRATAN */ - XK_Arabic_fatha = 0x05ee, /* U+064E ARABIC FATHA */ - XK_Arabic_damma = 0x05ef, /* U+064F ARABIC DAMMA */ - XK_Arabic_kasra = 0x05f0, /* U+0650 ARABIC KASRA */ - XK_Arabic_shadda = 0x05f1, /* U+0651 ARABIC SHADDA */ - XK_Arabic_sukun = 0x05f2, /* U+0652 ARABIC SUKUN */ - XK_Arabic_madda_above = 0x1000653, /* U+0653 ARABIC MADDAH ABOVE */ - XK_Arabic_hamza_above = 0x1000654, /* U+0654 ARABIC HAMZA ABOVE */ - XK_Arabic_hamza_below = 0x1000655, /* U+0655 ARABIC HAMZA BELOW */ - XK_Arabic_jeh = 0x1000698, /* U+0698 ARABIC LETTER JEH */ - XK_Arabic_veh = 0x10006a4, /* U+06A4 ARABIC LETTER VEH */ - XK_Arabic_keheh = 0x10006a9, /* U+06A9 ARABIC LETTER KEHEH */ - XK_Arabic_gaf = 0x10006af, /* U+06AF ARABIC LETTER GAF */ - XK_Arabic_noon_ghunna = 0x10006ba, /* U+06BA ARABIC LETTER NOON GHUNNA */ - XK_Arabic_heh_doachashmee = 0x10006be, /* U+06BE ARABIC LETTER HEH DOACHASHMEE */ - XK_Farsi_yeh = 0x10006cc, /* U+06CC ARABIC LETTER FARSI YEH */ - XK_Arabic_farsi_yeh = 0x10006cc, /* U+06CC ARABIC LETTER FARSI YEH */ - XK_Arabic_yeh_baree = 0x10006d2, /* U+06D2 ARABIC LETTER YEH BARREE */ - XK_Arabic_heh_goal = 0x10006c1, /* U+06C1 ARABIC LETTER HEH GOAL */ - XK_Arabic_switch = 0xff7e, /* Alias for mode_switch */ - XK_Cyrillic_GHE_bar = 0x1000492, /* U+0492 CYRILLIC CAPITAL LETTER GHE WITH STROKE */ - XK_Cyrillic_ghe_bar = 0x1000493, /* U+0493 CYRILLIC SMALL LETTER GHE WITH STROKE */ - XK_Cyrillic_ZHE_descender = 0x1000496, /* U+0496 CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER */ - XK_Cyrillic_zhe_descender = 0x1000497, /* U+0497 CYRILLIC SMALL LETTER ZHE WITH DESCENDER */ - XK_Cyrillic_KA_descender = 0x100049a, /* U+049A CYRILLIC CAPITAL LETTER KA WITH DESCENDER */ - XK_Cyrillic_ka_descender = 0x100049b, /* U+049B CYRILLIC SMALL LETTER KA WITH DESCENDER */ - XK_Cyrillic_KA_vertstroke = 0x100049c, /* U+049C CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE */ - XK_Cyrillic_ka_vertstroke = 0x100049d, /* U+049D CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE */ - XK_Cyrillic_EN_descender = 0x10004a2, /* U+04A2 CYRILLIC CAPITAL LETTER EN WITH DESCENDER */ - XK_Cyrillic_en_descender = 0x10004a3, /* U+04A3 CYRILLIC SMALL LETTER EN WITH DESCENDER */ - XK_Cyrillic_U_straight = 0x10004ae, /* U+04AE CYRILLIC CAPITAL LETTER STRAIGHT U */ - XK_Cyrillic_u_straight = 0x10004af, /* U+04AF CYRILLIC SMALL LETTER STRAIGHT U */ - XK_Cyrillic_U_straight_bar = 0x10004b0, /* U+04B0 CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE */ - XK_Cyrillic_u_straight_bar = 0x10004b1, /* U+04B1 CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE */ - XK_Cyrillic_HA_descender = 0x10004b2, /* U+04B2 CYRILLIC CAPITAL LETTER HA WITH DESCENDER */ - XK_Cyrillic_ha_descender = 0x10004b3, /* U+04B3 CYRILLIC SMALL LETTER HA WITH DESCENDER */ - XK_Cyrillic_CHE_descender = 0x10004b6, /* U+04B6 CYRILLIC CAPITAL LETTER CHE WITH DESCENDER */ - XK_Cyrillic_che_descender = 0x10004b7, /* U+04B7 CYRILLIC SMALL LETTER CHE WITH DESCENDER */ - XK_Cyrillic_CHE_vertstroke = 0x10004b8, /* U+04B8 CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE */ - XK_Cyrillic_che_vertstroke = 0x10004b9, /* U+04B9 CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE */ - XK_Cyrillic_SHHA = 0x10004ba, /* U+04BA CYRILLIC CAPITAL LETTER SHHA */ - XK_Cyrillic_shha = 0x10004bb, /* U+04BB CYRILLIC SMALL LETTER SHHA */ - XK_Cyrillic_SCHWA = 0x10004d8, /* U+04D8 CYRILLIC CAPITAL LETTER SCHWA */ - XK_Cyrillic_schwa = 0x10004d9, /* U+04D9 CYRILLIC SMALL LETTER SCHWA */ - XK_Cyrillic_I_macron = 0x10004e2, /* U+04E2 CYRILLIC CAPITAL LETTER I WITH MACRON */ - XK_Cyrillic_i_macron = 0x10004e3, /* U+04E3 CYRILLIC SMALL LETTER I WITH MACRON */ - XK_Cyrillic_O_bar = 0x10004e8, /* U+04E8 CYRILLIC CAPITAL LETTER BARRED O */ - XK_Cyrillic_o_bar = 0x10004e9, /* U+04E9 CYRILLIC SMALL LETTER BARRED O */ - XK_Cyrillic_U_macron = 0x10004ee, /* U+04EE CYRILLIC CAPITAL LETTER U WITH MACRON */ - XK_Cyrillic_u_macron = 0x10004ef, /* U+04EF CYRILLIC SMALL LETTER U WITH MACRON */ - XK_Serbian_dje = 0x06a1, /* U+0452 CYRILLIC SMALL LETTER DJE */ - XK_Macedonia_gje = 0x06a2, /* U+0453 CYRILLIC SMALL LETTER GJE */ - XK_Cyrillic_io = 0x06a3, /* U+0451 CYRILLIC SMALL LETTER IO */ - XK_Ukrainian_ie = 0x06a4, /* U+0454 CYRILLIC SMALL LETTER UKRAINIAN IE */ - XK_Ukranian_je = 0x06a4, /* deprecated */ - XK_Macedonia_dse = 0x06a5, /* U+0455 CYRILLIC SMALL LETTER DZE */ - XK_Ukrainian_i = 0x06a6, /* U+0456 CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ - XK_Ukranian_i = 0x06a6, /* deprecated */ - XK_Ukrainian_yi = 0x06a7, /* U+0457 CYRILLIC SMALL LETTER YI */ - XK_Ukranian_yi = 0x06a7, /* deprecated */ - XK_Cyrillic_je = 0x06a8, /* U+0458 CYRILLIC SMALL LETTER JE */ - XK_Serbian_je = 0x06a8, /* deprecated */ - XK_Cyrillic_lje = 0x06a9, /* U+0459 CYRILLIC SMALL LETTER LJE */ - XK_Serbian_lje = 0x06a9, /* deprecated */ - XK_Cyrillic_nje = 0x06aa, /* U+045A CYRILLIC SMALL LETTER NJE */ - XK_Serbian_nje = 0x06aa, /* deprecated */ - XK_Serbian_tshe = 0x06ab, /* U+045B CYRILLIC SMALL LETTER TSHE */ - XK_Macedonia_kje = 0x06ac, /* U+045C CYRILLIC SMALL LETTER KJE */ - XK_Ukrainian_ghe_with_upturn = 0x06ad, /* U+0491 CYRILLIC SMALL LETTER GHE WITH UPTURN */ - XK_Byelorussian_shortu = 0x06ae, /* U+045E CYRILLIC SMALL LETTER SHORT U */ - XK_Cyrillic_dzhe = 0x06af, /* U+045F CYRILLIC SMALL LETTER DZHE */ - XK_Serbian_dze = 0x06af, /* deprecated */ - XK_numerosign = 0x06b0, /* U+2116 NUMERO SIGN */ - XK_Serbian_DJE = 0x06b1, /* U+0402 CYRILLIC CAPITAL LETTER DJE */ - XK_Macedonia_GJE = 0x06b2, /* U+0403 CYRILLIC CAPITAL LETTER GJE */ - XK_Cyrillic_IO = 0x06b3, /* U+0401 CYRILLIC CAPITAL LETTER IO */ - XK_Ukrainian_IE = 0x06b4, /* U+0404 CYRILLIC CAPITAL LETTER UKRAINIAN IE */ - XK_Ukranian_JE = 0x06b4, /* deprecated */ - XK_Macedonia_DSE = 0x06b5, /* U+0405 CYRILLIC CAPITAL LETTER DZE */ - XK_Ukrainian_I = 0x06b6, /* U+0406 CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ - XK_Ukranian_I = 0x06b6, /* deprecated */ - XK_Ukrainian_YI = 0x06b7, /* U+0407 CYRILLIC CAPITAL LETTER YI */ - XK_Ukranian_YI = 0x06b7, /* deprecated */ - XK_Cyrillic_JE = 0x06b8, /* U+0408 CYRILLIC CAPITAL LETTER JE */ - XK_Serbian_JE = 0x06b8, /* deprecated */ - XK_Cyrillic_LJE = 0x06b9, /* U+0409 CYRILLIC CAPITAL LETTER LJE */ - XK_Serbian_LJE = 0x06b9, /* deprecated */ - XK_Cyrillic_NJE = 0x06ba, /* U+040A CYRILLIC CAPITAL LETTER NJE */ - XK_Serbian_NJE = 0x06ba, /* deprecated */ - XK_Serbian_TSHE = 0x06bb, /* U+040B CYRILLIC CAPITAL LETTER TSHE */ - XK_Macedonia_KJE = 0x06bc, /* U+040C CYRILLIC CAPITAL LETTER KJE */ - XK_Ukrainian_GHE_WITH_UPTURN = 0x06bd, /* U+0490 CYRILLIC CAPITAL LETTER GHE WITH UPTURN */ - XK_Byelorussian_SHORTU = 0x06be, /* U+040E CYRILLIC CAPITAL LETTER SHORT U */ - XK_Cyrillic_DZHE = 0x06bf, /* U+040F CYRILLIC CAPITAL LETTER DZHE */ - XK_Serbian_DZE = 0x06bf, /* deprecated */ - XK_Cyrillic_yu = 0x06c0, /* U+044E CYRILLIC SMALL LETTER YU */ - XK_Cyrillic_a = 0x06c1, /* U+0430 CYRILLIC SMALL LETTER A */ - XK_Cyrillic_be = 0x06c2, /* U+0431 CYRILLIC SMALL LETTER BE */ - XK_Cyrillic_tse = 0x06c3, /* U+0446 CYRILLIC SMALL LETTER TSE */ - XK_Cyrillic_de = 0x06c4, /* U+0434 CYRILLIC SMALL LETTER DE */ - XK_Cyrillic_ie = 0x06c5, /* U+0435 CYRILLIC SMALL LETTER IE */ - XK_Cyrillic_ef = 0x06c6, /* U+0444 CYRILLIC SMALL LETTER EF */ - XK_Cyrillic_ghe = 0x06c7, /* U+0433 CYRILLIC SMALL LETTER GHE */ - XK_Cyrillic_ha = 0x06c8, /* U+0445 CYRILLIC SMALL LETTER HA */ - XK_Cyrillic_i = 0x06c9, /* U+0438 CYRILLIC SMALL LETTER I */ - XK_Cyrillic_shorti = 0x06ca, /* U+0439 CYRILLIC SMALL LETTER SHORT I */ - XK_Cyrillic_ka = 0x06cb, /* U+043A CYRILLIC SMALL LETTER KA */ - XK_Cyrillic_el = 0x06cc, /* U+043B CYRILLIC SMALL LETTER EL */ - XK_Cyrillic_em = 0x06cd, /* U+043C CYRILLIC SMALL LETTER EM */ - XK_Cyrillic_en = 0x06ce, /* U+043D CYRILLIC SMALL LETTER EN */ - XK_Cyrillic_o = 0x06cf, /* U+043E CYRILLIC SMALL LETTER O */ - XK_Cyrillic_pe = 0x06d0, /* U+043F CYRILLIC SMALL LETTER PE */ - XK_Cyrillic_ya = 0x06d1, /* U+044F CYRILLIC SMALL LETTER YA */ - XK_Cyrillic_er = 0x06d2, /* U+0440 CYRILLIC SMALL LETTER ER */ - XK_Cyrillic_es = 0x06d3, /* U+0441 CYRILLIC SMALL LETTER ES */ - XK_Cyrillic_te = 0x06d4, /* U+0442 CYRILLIC SMALL LETTER TE */ - XK_Cyrillic_u = 0x06d5, /* U+0443 CYRILLIC SMALL LETTER U */ - XK_Cyrillic_zhe = 0x06d6, /* U+0436 CYRILLIC SMALL LETTER ZHE */ - XK_Cyrillic_ve = 0x06d7, /* U+0432 CYRILLIC SMALL LETTER VE */ - XK_Cyrillic_softsign = 0x06d8, /* U+044C CYRILLIC SMALL LETTER SOFT SIGN */ - XK_Cyrillic_yeru = 0x06d9, /* U+044B CYRILLIC SMALL LETTER YERU */ - XK_Cyrillic_ze = 0x06da, /* U+0437 CYRILLIC SMALL LETTER ZE */ - XK_Cyrillic_sha = 0x06db, /* U+0448 CYRILLIC SMALL LETTER SHA */ - XK_Cyrillic_e = 0x06dc, /* U+044D CYRILLIC SMALL LETTER E */ - XK_Cyrillic_shcha = 0x06dd, /* U+0449 CYRILLIC SMALL LETTER SHCHA */ - XK_Cyrillic_che = 0x06de, /* U+0447 CYRILLIC SMALL LETTER CHE */ - XK_Cyrillic_hardsign = 0x06df, /* U+044A CYRILLIC SMALL LETTER HARD SIGN */ - XK_Cyrillic_YU = 0x06e0, /* U+042E CYRILLIC CAPITAL LETTER YU */ - XK_Cyrillic_A = 0x06e1, /* U+0410 CYRILLIC CAPITAL LETTER A */ - XK_Cyrillic_BE = 0x06e2, /* U+0411 CYRILLIC CAPITAL LETTER BE */ - XK_Cyrillic_TSE = 0x06e3, /* U+0426 CYRILLIC CAPITAL LETTER TSE */ - XK_Cyrillic_DE = 0x06e4, /* U+0414 CYRILLIC CAPITAL LETTER DE */ - XK_Cyrillic_IE = 0x06e5, /* U+0415 CYRILLIC CAPITAL LETTER IE */ - XK_Cyrillic_EF = 0x06e6, /* U+0424 CYRILLIC CAPITAL LETTER EF */ - XK_Cyrillic_GHE = 0x06e7, /* U+0413 CYRILLIC CAPITAL LETTER GHE */ - XK_Cyrillic_HA = 0x06e8, /* U+0425 CYRILLIC CAPITAL LETTER HA */ - XK_Cyrillic_I = 0x06e9, /* U+0418 CYRILLIC CAPITAL LETTER I */ - XK_Cyrillic_SHORTI = 0x06ea, /* U+0419 CYRILLIC CAPITAL LETTER SHORT I */ - XK_Cyrillic_KA = 0x06eb, /* U+041A CYRILLIC CAPITAL LETTER KA */ - XK_Cyrillic_EL = 0x06ec, /* U+041B CYRILLIC CAPITAL LETTER EL */ - XK_Cyrillic_EM = 0x06ed, /* U+041C CYRILLIC CAPITAL LETTER EM */ - XK_Cyrillic_EN = 0x06ee, /* U+041D CYRILLIC CAPITAL LETTER EN */ - XK_Cyrillic_O = 0x06ef, /* U+041E CYRILLIC CAPITAL LETTER O */ - XK_Cyrillic_PE = 0x06f0, /* U+041F CYRILLIC CAPITAL LETTER PE */ - XK_Cyrillic_YA = 0x06f1, /* U+042F CYRILLIC CAPITAL LETTER YA */ - XK_Cyrillic_ER = 0x06f2, /* U+0420 CYRILLIC CAPITAL LETTER ER */ - XK_Cyrillic_ES = 0x06f3, /* U+0421 CYRILLIC CAPITAL LETTER ES */ - XK_Cyrillic_TE = 0x06f4, /* U+0422 CYRILLIC CAPITAL LETTER TE */ - XK_Cyrillic_U = 0x06f5, /* U+0423 CYRILLIC CAPITAL LETTER U */ - XK_Cyrillic_ZHE = 0x06f6, /* U+0416 CYRILLIC CAPITAL LETTER ZHE */ - XK_Cyrillic_VE = 0x06f7, /* U+0412 CYRILLIC CAPITAL LETTER VE */ - XK_Cyrillic_SOFTSIGN = 0x06f8, /* U+042C CYRILLIC CAPITAL LETTER SOFT SIGN */ - XK_Cyrillic_YERU = 0x06f9, /* U+042B CYRILLIC CAPITAL LETTER YERU */ - XK_Cyrillic_ZE = 0x06fa, /* U+0417 CYRILLIC CAPITAL LETTER ZE */ - XK_Cyrillic_SHA = 0x06fb, /* U+0428 CYRILLIC CAPITAL LETTER SHA */ - XK_Cyrillic_E = 0x06fc, /* U+042D CYRILLIC CAPITAL LETTER E */ - XK_Cyrillic_SHCHA = 0x06fd, /* U+0429 CYRILLIC CAPITAL LETTER SHCHA */ - XK_Cyrillic_CHE = 0x06fe, /* U+0427 CYRILLIC CAPITAL LETTER CHE */ - XK_Cyrillic_HARDSIGN = 0x06ff, /* U+042A CYRILLIC CAPITAL LETTER HARD SIGN */ - XK_Greek_ALPHAaccent = 0x07a1, /* U+0386 GREEK CAPITAL LETTER ALPHA WITH TONOS */ - XK_Greek_EPSILONaccent = 0x07a2, /* U+0388 GREEK CAPITAL LETTER EPSILON WITH TONOS */ - XK_Greek_ETAaccent = 0x07a3, /* U+0389 GREEK CAPITAL LETTER ETA WITH TONOS */ - XK_Greek_IOTAaccent = 0x07a4, /* U+038A GREEK CAPITAL LETTER IOTA WITH TONOS */ - XK_Greek_IOTAdieresis = 0x07a5, /* U+03AA GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ - XK_Greek_IOTAdiaeresis = 0x07a5, /* old typo */ - XK_Greek_OMICRONaccent = 0x07a7, /* U+038C GREEK CAPITAL LETTER OMICRON WITH TONOS */ - XK_Greek_UPSILONaccent = 0x07a8, /* U+038E GREEK CAPITAL LETTER UPSILON WITH TONOS */ - XK_Greek_UPSILONdieresis = 0x07a9, /* U+03AB GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ - XK_Greek_OMEGAaccent = 0x07ab, /* U+038F GREEK CAPITAL LETTER OMEGA WITH TONOS */ - XK_Greek_accentdieresis = 0x07ae, /* U+0385 GREEK DIALYTIKA TONOS */ - XK_Greek_horizbar = 0x07af, /* U+2015 HORIZONTAL BAR */ - XK_Greek_alphaaccent = 0x07b1, /* U+03AC GREEK SMALL LETTER ALPHA WITH TONOS */ - XK_Greek_epsilonaccent = 0x07b2, /* U+03AD GREEK SMALL LETTER EPSILON WITH TONOS */ - XK_Greek_etaaccent = 0x07b3, /* U+03AE GREEK SMALL LETTER ETA WITH TONOS */ - XK_Greek_iotaaccent = 0x07b4, /* U+03AF GREEK SMALL LETTER IOTA WITH TONOS */ - XK_Greek_iotadieresis = 0x07b5, /* U+03CA GREEK SMALL LETTER IOTA WITH DIALYTIKA */ - XK_Greek_iotaaccentdieresis = 0x07b6, /* U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ - XK_Greek_omicronaccent = 0x07b7, /* U+03CC GREEK SMALL LETTER OMICRON WITH TONOS */ - XK_Greek_upsilonaccent = 0x07b8, /* U+03CD GREEK SMALL LETTER UPSILON WITH TONOS */ - XK_Greek_upsilondieresis = 0x07b9, /* U+03CB GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ - XK_Greek_upsilonaccentdieresis = 0x07ba, /* U+03B0 GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ - XK_Greek_omegaaccent = 0x07bb, /* U+03CE GREEK SMALL LETTER OMEGA WITH TONOS */ - XK_Greek_ALPHA = 0x07c1, /* U+0391 GREEK CAPITAL LETTER ALPHA */ - XK_Greek_BETA = 0x07c2, /* U+0392 GREEK CAPITAL LETTER BETA */ - XK_Greek_GAMMA = 0x07c3, /* U+0393 GREEK CAPITAL LETTER GAMMA */ - XK_Greek_DELTA = 0x07c4, /* U+0394 GREEK CAPITAL LETTER DELTA */ - XK_Greek_EPSILON = 0x07c5, /* U+0395 GREEK CAPITAL LETTER EPSILON */ - XK_Greek_ZETA = 0x07c6, /* U+0396 GREEK CAPITAL LETTER ZETA */ - XK_Greek_ETA = 0x07c7, /* U+0397 GREEK CAPITAL LETTER ETA */ - XK_Greek_THETA = 0x07c8, /* U+0398 GREEK CAPITAL LETTER THETA */ - XK_Greek_IOTA = 0x07c9, /* U+0399 GREEK CAPITAL LETTER IOTA */ - XK_Greek_KAPPA = 0x07ca, /* U+039A GREEK CAPITAL LETTER KAPPA */ - XK_Greek_LAMDA = 0x07cb, /* U+039B GREEK CAPITAL LETTER LAMDA */ - XK_Greek_LAMBDA = 0x07cb, /* U+039B GREEK CAPITAL LETTER LAMDA */ - XK_Greek_MU = 0x07cc, /* U+039C GREEK CAPITAL LETTER MU */ - XK_Greek_NU = 0x07cd, /* U+039D GREEK CAPITAL LETTER NU */ - XK_Greek_XI = 0x07ce, /* U+039E GREEK CAPITAL LETTER XI */ - XK_Greek_OMICRON = 0x07cf, /* U+039F GREEK CAPITAL LETTER OMICRON */ - XK_Greek_PI = 0x07d0, /* U+03A0 GREEK CAPITAL LETTER PI */ - XK_Greek_RHO = 0x07d1, /* U+03A1 GREEK CAPITAL LETTER RHO */ - XK_Greek_SIGMA = 0x07d2, /* U+03A3 GREEK CAPITAL LETTER SIGMA */ - XK_Greek_TAU = 0x07d4, /* U+03A4 GREEK CAPITAL LETTER TAU */ - XK_Greek_UPSILON = 0x07d5, /* U+03A5 GREEK CAPITAL LETTER UPSILON */ - XK_Greek_PHI = 0x07d6, /* U+03A6 GREEK CAPITAL LETTER PHI */ - XK_Greek_CHI = 0x07d7, /* U+03A7 GREEK CAPITAL LETTER CHI */ - XK_Greek_PSI = 0x07d8, /* U+03A8 GREEK CAPITAL LETTER PSI */ - XK_Greek_OMEGA = 0x07d9, /* U+03A9 GREEK CAPITAL LETTER OMEGA */ - XK_Greek_alpha = 0x07e1, /* U+03B1 GREEK SMALL LETTER ALPHA */ - XK_Greek_beta = 0x07e2, /* U+03B2 GREEK SMALL LETTER BETA */ - XK_Greek_gamma = 0x07e3, /* U+03B3 GREEK SMALL LETTER GAMMA */ - XK_Greek_delta = 0x07e4, /* U+03B4 GREEK SMALL LETTER DELTA */ - XK_Greek_epsilon = 0x07e5, /* U+03B5 GREEK SMALL LETTER EPSILON */ - XK_Greek_zeta = 0x07e6, /* U+03B6 GREEK SMALL LETTER ZETA */ - XK_Greek_eta = 0x07e7, /* U+03B7 GREEK SMALL LETTER ETA */ - XK_Greek_theta = 0x07e8, /* U+03B8 GREEK SMALL LETTER THETA */ - XK_Greek_iota = 0x07e9, /* U+03B9 GREEK SMALL LETTER IOTA */ - XK_Greek_kappa = 0x07ea, /* U+03BA GREEK SMALL LETTER KAPPA */ - XK_Greek_lamda = 0x07eb, /* U+03BB GREEK SMALL LETTER LAMDA */ - XK_Greek_lambda = 0x07eb, /* U+03BB GREEK SMALL LETTER LAMDA */ - XK_Greek_mu = 0x07ec, /* U+03BC GREEK SMALL LETTER MU */ - XK_Greek_nu = 0x07ed, /* U+03BD GREEK SMALL LETTER NU */ - XK_Greek_xi = 0x07ee, /* U+03BE GREEK SMALL LETTER XI */ - XK_Greek_omicron = 0x07ef, /* U+03BF GREEK SMALL LETTER OMICRON */ - XK_Greek_pi = 0x07f0, /* U+03C0 GREEK SMALL LETTER PI */ - XK_Greek_rho = 0x07f1, /* U+03C1 GREEK SMALL LETTER RHO */ - XK_Greek_sigma = 0x07f2, /* U+03C3 GREEK SMALL LETTER SIGMA */ - XK_Greek_finalsmallsigma = 0x07f3, /* U+03C2 GREEK SMALL LETTER FINAL SIGMA */ - XK_Greek_tau = 0x07f4, /* U+03C4 GREEK SMALL LETTER TAU */ - XK_Greek_upsilon = 0x07f5, /* U+03C5 GREEK SMALL LETTER UPSILON */ - XK_Greek_phi = 0x07f6, /* U+03C6 GREEK SMALL LETTER PHI */ - XK_Greek_chi = 0x07f7, /* U+03C7 GREEK SMALL LETTER CHI */ - XK_Greek_psi = 0x07f8, /* U+03C8 GREEK SMALL LETTER PSI */ - XK_Greek_omega = 0x07f9, /* U+03C9 GREEK SMALL LETTER OMEGA */ - XK_Greek_switch = 0xff7e, /* Alias for mode_switch */ - XK_leftradical = 0x08a1, /* U+23B7 RADICAL SYMBOL BOTTOM */ - XK_topleftradical = 0x08a2, /*(U+250C BOX DRAWINGS LIGHT DOWN AND RIGHT)*/ - XK_horizconnector = 0x08a3, /*(U+2500 BOX DRAWINGS LIGHT HORIZONTAL)*/ - XK_topintegral = 0x08a4, /* U+2320 TOP HALF INTEGRAL */ - XK_botintegral = 0x08a5, /* U+2321 BOTTOM HALF INTEGRAL */ - XK_vertconnector = 0x08a6, /*(U+2502 BOX DRAWINGS LIGHT VERTICAL)*/ - XK_topleftsqbracket = 0x08a7, /* U+23A1 LEFT SQUARE BRACKET UPPER CORNER */ - XK_botleftsqbracket = 0x08a8, /* U+23A3 LEFT SQUARE BRACKET LOWER CORNER */ - XK_toprightsqbracket = 0x08a9, /* U+23A4 RIGHT SQUARE BRACKET UPPER CORNER */ - XK_botrightsqbracket = 0x08aa, /* U+23A6 RIGHT SQUARE BRACKET LOWER CORNER */ - XK_topleftparens = 0x08ab, /* U+239B LEFT PARENTHESIS UPPER HOOK */ - XK_botleftparens = 0x08ac, /* U+239D LEFT PARENTHESIS LOWER HOOK */ - XK_toprightparens = 0x08ad, /* U+239E RIGHT PARENTHESIS UPPER HOOK */ - XK_botrightparens = 0x08ae, /* U+23A0 RIGHT PARENTHESIS LOWER HOOK */ - XK_leftmiddlecurlybrace = 0x08af, /* U+23A8 LEFT CURLY BRACKET MIDDLE PIECE */ - XK_rightmiddlecurlybrace = 0x08b0, /* U+23AC RIGHT CURLY BRACKET MIDDLE PIECE */ - XK_topleftsummation = 0x08b1, - XK_botleftsummation = 0x08b2, - XK_topvertsummationconnector = 0x08b3, - XK_botvertsummationconnector = 0x08b4, - XK_toprightsummation = 0x08b5, - XK_botrightsummation = 0x08b6, - XK_rightmiddlesummation = 0x08b7, - XK_lessthanequal = 0x08bc, /* U+2264 LESS-THAN OR EQUAL TO */ - XK_notequal = 0x08bd, /* U+2260 NOT EQUAL TO */ - XK_greaterthanequal = 0x08be, /* U+2265 GREATER-THAN OR EQUAL TO */ - XK_integral = 0x08bf, /* U+222B INTEGRAL */ - XK_therefore = 0x08c0, /* U+2234 THEREFORE */ - XK_variation = 0x08c1, /* U+221D PROPORTIONAL TO */ - XK_infinity = 0x08c2, /* U+221E INFINITY */ - XK_nabla = 0x08c5, /* U+2207 NABLA */ - XK_approximate = 0x08c8, /* U+223C TILDE OPERATOR */ - XK_similarequal = 0x08c9, /* U+2243 ASYMPTOTICALLY EQUAL TO */ - XK_ifonlyif = 0x08cd, /* U+21D4 LEFT RIGHT DOUBLE ARROW */ - XK_implies = 0x08ce, /* U+21D2 RIGHTWARDS DOUBLE ARROW */ - XK_identical = 0x08cf, /* U+2261 IDENTICAL TO */ - XK_radical = 0x08d6, /* U+221A SQUARE ROOT */ - XK_includedin = 0x08da, /* U+2282 SUBSET OF */ - XK_includes = 0x08db, /* U+2283 SUPERSET OF */ - XK_intersection = 0x08dc, /* U+2229 INTERSECTION */ - XK_union = 0x08dd, /* U+222A UNION */ - XK_logicaland = 0x08de, /* U+2227 LOGICAL AND */ - XK_logicalor = 0x08df, /* U+2228 LOGICAL OR */ - XK_partialderivative = 0x08ef, /* U+2202 PARTIAL DIFFERENTIAL */ - XK_function = 0x08f6, /* U+0192 LATIN SMALL LETTER F WITH HOOK */ - XK_leftarrow = 0x08fb, /* U+2190 LEFTWARDS ARROW */ - XK_uparrow = 0x08fc, /* U+2191 UPWARDS ARROW */ - XK_rightarrow = 0x08fd, /* U+2192 RIGHTWARDS ARROW */ - XK_downarrow = 0x08fe, /* U+2193 DOWNWARDS ARROW */ - XK_blank = 0x09df, - XK_soliddiamond = 0x09e0, /* U+25C6 BLACK DIAMOND */ - XK_checkerboard = 0x09e1, /* U+2592 MEDIUM SHADE */ - XK_ht = 0x09e2, /* U+2409 SYMBOL FOR HORIZONTAL TABULATION */ - XK_ff = 0x09e3, /* U+240C SYMBOL FOR FORM FEED */ - XK_cr = 0x09e4, /* U+240D SYMBOL FOR CARRIAGE RETURN */ - XK_lf = 0x09e5, /* U+240A SYMBOL FOR LINE FEED */ - XK_nl = 0x09e8, /* U+2424 SYMBOL FOR NEWLINE */ - XK_vt = 0x09e9, /* U+240B SYMBOL FOR VERTICAL TABULATION */ - XK_lowrightcorner = 0x09ea, /* U+2518 BOX DRAWINGS LIGHT UP AND LEFT */ - XK_uprightcorner = 0x09eb, /* U+2510 BOX DRAWINGS LIGHT DOWN AND LEFT */ - XK_upleftcorner = 0x09ec, /* U+250C BOX DRAWINGS LIGHT DOWN AND RIGHT */ - XK_lowleftcorner = 0x09ed, /* U+2514 BOX DRAWINGS LIGHT UP AND RIGHT */ - XK_crossinglines = 0x09ee, /* U+253C BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ - XK_horizlinescan1 = 0x09ef, /* U+23BA HORIZONTAL SCAN LINE-1 */ - XK_horizlinescan3 = 0x09f0, /* U+23BB HORIZONTAL SCAN LINE-3 */ - XK_horizlinescan5 = 0x09f1, /* U+2500 BOX DRAWINGS LIGHT HORIZONTAL */ - XK_horizlinescan7 = 0x09f2, /* U+23BC HORIZONTAL SCAN LINE-7 */ - XK_horizlinescan9 = 0x09f3, /* U+23BD HORIZONTAL SCAN LINE-9 */ - XK_leftt = 0x09f4, /* U+251C BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ - XK_rightt = 0x09f5, /* U+2524 BOX DRAWINGS LIGHT VERTICAL AND LEFT */ - XK_bott = 0x09f6, /* U+2534 BOX DRAWINGS LIGHT UP AND HORIZONTAL */ - XK_topt = 0x09f7, /* U+252C BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ - XK_vertbar = 0x09f8, /* U+2502 BOX DRAWINGS LIGHT VERTICAL */ - XK_emspace = 0x0aa1, /* U+2003 EM SPACE */ - XK_enspace = 0x0aa2, /* U+2002 EN SPACE */ - XK_em3space = 0x0aa3, /* U+2004 THREE-PER-EM SPACE */ - XK_em4space = 0x0aa4, /* U+2005 FOUR-PER-EM SPACE */ - XK_digitspace = 0x0aa5, /* U+2007 FIGURE SPACE */ - XK_punctspace = 0x0aa6, /* U+2008 PUNCTUATION SPACE */ - XK_thinspace = 0x0aa7, /* U+2009 THIN SPACE */ - XK_hairspace = 0x0aa8, /* U+200A HAIR SPACE */ - XK_emdash = 0x0aa9, /* U+2014 EM DASH */ - XK_endash = 0x0aaa, /* U+2013 EN DASH */ - XK_signifblank = 0x0aac, /*(U+2423 OPEN BOX)*/ - XK_ellipsis = 0x0aae, /* U+2026 HORIZONTAL ELLIPSIS */ - XK_doubbaselinedot = 0x0aaf, /* U+2025 TWO DOT LEADER */ - XK_onethird = 0x0ab0, /* U+2153 VULGAR FRACTION ONE THIRD */ - XK_twothirds = 0x0ab1, /* U+2154 VULGAR FRACTION TWO THIRDS */ - XK_onefifth = 0x0ab2, /* U+2155 VULGAR FRACTION ONE FIFTH */ - XK_twofifths = 0x0ab3, /* U+2156 VULGAR FRACTION TWO FIFTHS */ - XK_threefifths = 0x0ab4, /* U+2157 VULGAR FRACTION THREE FIFTHS */ - XK_fourfifths = 0x0ab5, /* U+2158 VULGAR FRACTION FOUR FIFTHS */ - XK_onesixth = 0x0ab6, /* U+2159 VULGAR FRACTION ONE SIXTH */ - XK_fivesixths = 0x0ab7, /* U+215A VULGAR FRACTION FIVE SIXTHS */ - XK_careof = 0x0ab8, /* U+2105 CARE OF */ - XK_figdash = 0x0abb, /* U+2012 FIGURE DASH */ - XK_leftanglebracket = 0x0abc, /*(U+27E8 MATHEMATICAL LEFT ANGLE BRACKET)*/ - XK_decimalpoint = 0x0abd, /*(U+002E FULL STOP)*/ - XK_rightanglebracket = 0x0abe, /*(U+27E9 MATHEMATICAL RIGHT ANGLE BRACKET)*/ - XK_marker = 0x0abf, - XK_oneeighth = 0x0ac3, /* U+215B VULGAR FRACTION ONE EIGHTH */ - XK_threeeighths = 0x0ac4, /* U+215C VULGAR FRACTION THREE EIGHTHS */ - XK_fiveeighths = 0x0ac5, /* U+215D VULGAR FRACTION FIVE EIGHTHS */ - XK_seveneighths = 0x0ac6, /* U+215E VULGAR FRACTION SEVEN EIGHTHS */ - XK_trademark = 0x0ac9, /* U+2122 TRADE MARK SIGN */ - XK_signaturemark = 0x0aca, /*(U+2613 SALTIRE)*/ - XK_trademarkincircle = 0x0acb, - XK_leftopentriangle = 0x0acc, /*(U+25C1 WHITE LEFT-POINTING TRIANGLE)*/ - XK_rightopentriangle = 0x0acd, /*(U+25B7 WHITE RIGHT-POINTING TRIANGLE)*/ - XK_emopencircle = 0x0ace, /*(U+25CB WHITE CIRCLE)*/ - XK_emopenrectangle = 0x0acf, /*(U+25AF WHITE VERTICAL RECTANGLE)*/ - XK_leftsinglequotemark = 0x0ad0, /* U+2018 LEFT SINGLE QUOTATION MARK */ - XK_rightsinglequotemark = 0x0ad1, /* U+2019 RIGHT SINGLE QUOTATION MARK */ - XK_leftdoublequotemark = 0x0ad2, /* U+201C LEFT DOUBLE QUOTATION MARK */ - XK_rightdoublequotemark = 0x0ad3, /* U+201D RIGHT DOUBLE QUOTATION MARK */ - XK_prescription = 0x0ad4, /* U+211E PRESCRIPTION TAKE */ - XK_minutes = 0x0ad6, /* U+2032 PRIME */ - XK_seconds = 0x0ad7, /* U+2033 DOUBLE PRIME */ - XK_latincross = 0x0ad9, /* U+271D LATIN CROSS */ - XK_hexagram = 0x0ada, - XK_filledrectbullet = 0x0adb, /*(U+25AC BLACK RECTANGLE)*/ - XK_filledlefttribullet = 0x0adc, /*(U+25C0 BLACK LEFT-POINTING TRIANGLE)*/ - XK_filledrighttribullet = 0x0add, /*(U+25B6 BLACK RIGHT-POINTING TRIANGLE)*/ - XK_emfilledcircle = 0x0ade, /*(U+25CF BLACK CIRCLE)*/ - XK_emfilledrect = 0x0adf, /*(U+25AE BLACK VERTICAL RECTANGLE)*/ - XK_enopencircbullet = 0x0ae0, /*(U+25E6 WHITE BULLET)*/ - XK_enopensquarebullet = 0x0ae1, /*(U+25AB WHITE SMALL SQUARE)*/ - XK_openrectbullet = 0x0ae2, /*(U+25AD WHITE RECTANGLE)*/ - XK_opentribulletup = 0x0ae3, /*(U+25B3 WHITE UP-POINTING TRIANGLE)*/ - XK_opentribulletdown = 0x0ae4, /*(U+25BD WHITE DOWN-POINTING TRIANGLE)*/ - XK_openstar = 0x0ae5, /*(U+2606 WHITE STAR)*/ - XK_enfilledcircbullet = 0x0ae6, /*(U+2022 BULLET)*/ - XK_enfilledsqbullet = 0x0ae7, /*(U+25AA BLACK SMALL SQUARE)*/ - XK_filledtribulletup = 0x0ae8, /*(U+25B2 BLACK UP-POINTING TRIANGLE)*/ - XK_filledtribulletdown = 0x0ae9, /*(U+25BC BLACK DOWN-POINTING TRIANGLE)*/ - XK_leftpointer = 0x0aea, /*(U+261C WHITE LEFT POINTING INDEX)*/ - XK_rightpointer = 0x0aeb, /*(U+261E WHITE RIGHT POINTING INDEX)*/ - XK_club = 0x0aec, /* U+2663 BLACK CLUB SUIT */ - XK_diamond = 0x0aed, /* U+2666 BLACK DIAMOND SUIT */ - XK_heart = 0x0aee, /* U+2665 BLACK HEART SUIT */ - XK_maltesecross = 0x0af0, /* U+2720 MALTESE CROSS */ - XK_dagger = 0x0af1, /* U+2020 DAGGER */ - XK_doubledagger = 0x0af2, /* U+2021 DOUBLE DAGGER */ - XK_checkmark = 0x0af3, /* U+2713 CHECK MARK */ - XK_ballotcross = 0x0af4, /* U+2717 BALLOT X */ - XK_musicalsharp = 0x0af5, /* U+266F MUSIC SHARP SIGN */ - XK_musicalflat = 0x0af6, /* U+266D MUSIC FLAT SIGN */ - XK_malesymbol = 0x0af7, /* U+2642 MALE SIGN */ - XK_femalesymbol = 0x0af8, /* U+2640 FEMALE SIGN */ - XK_telephone = 0x0af9, /* U+260E BLACK TELEPHONE */ - XK_telephonerecorder = 0x0afa, /* U+2315 TELEPHONE RECORDER */ - XK_phonographcopyright = 0x0afb, /* U+2117 SOUND RECORDING COPYRIGHT */ - XK_caret = 0x0afc, /* U+2038 CARET */ - XK_singlelowquotemark = 0x0afd, /* U+201A SINGLE LOW-9 QUOTATION MARK */ - XK_doublelowquotemark = 0x0afe, /* U+201E DOUBLE LOW-9 QUOTATION MARK */ - XK_cursor = 0x0aff, - XK_leftcaret = 0x0ba3, /*(U+003C LESS-THAN SIGN)*/ - XK_rightcaret = 0x0ba6, /*(U+003E GREATER-THAN SIGN)*/ - XK_downcaret = 0x0ba8, /*(U+2228 LOGICAL OR)*/ - XK_upcaret = 0x0ba9, /*(U+2227 LOGICAL AND)*/ - XK_overbar = 0x0bc0, /*(U+00AF MACRON)*/ - XK_downtack = 0x0bc2, /* U+22A5 UP TACK */ - XK_upshoe = 0x0bc3, /*(U+2229 INTERSECTION)*/ - XK_downstile = 0x0bc4, /* U+230A LEFT FLOOR */ - XK_underbar = 0x0bc6, /*(U+005F LOW LINE)*/ - XK_jot = 0x0bca, /* U+2218 RING OPERATOR */ - XK_quad = 0x0bcc, /* U+2395 APL FUNCTIONAL SYMBOL QUAD */ - XK_uptack = 0x0bce, /* U+22A4 DOWN TACK */ - XK_circle = 0x0bcf, /* U+25CB WHITE CIRCLE */ - XK_upstile = 0x0bd3, /* U+2308 LEFT CEILING */ - XK_downshoe = 0x0bd6, /*(U+222A UNION)*/ - XK_rightshoe = 0x0bd8, /*(U+2283 SUPERSET OF)*/ - XK_leftshoe = 0x0bda, /*(U+2282 SUBSET OF)*/ - XK_lefttack = 0x0bdc, /* U+22A2 RIGHT TACK */ - XK_righttack = 0x0bfc, /* U+22A3 LEFT TACK */ - XK_hebrew_doublelowline = 0x0cdf, /* U+2017 DOUBLE LOW LINE */ - XK_hebrew_aleph = 0x0ce0, /* U+05D0 HEBREW LETTER ALEF */ - XK_hebrew_bet = 0x0ce1, /* U+05D1 HEBREW LETTER BET */ - XK_hebrew_beth = 0x0ce1, /* deprecated */ - XK_hebrew_gimel = 0x0ce2, /* U+05D2 HEBREW LETTER GIMEL */ - XK_hebrew_gimmel = 0x0ce2, /* deprecated */ - XK_hebrew_dalet = 0x0ce3, /* U+05D3 HEBREW LETTER DALET */ - XK_hebrew_daleth = 0x0ce3, /* deprecated */ - XK_hebrew_he = 0x0ce4, /* U+05D4 HEBREW LETTER HE */ - XK_hebrew_waw = 0x0ce5, /* U+05D5 HEBREW LETTER VAV */ - XK_hebrew_zain = 0x0ce6, /* U+05D6 HEBREW LETTER ZAYIN */ - XK_hebrew_zayin = 0x0ce6, /* deprecated */ - XK_hebrew_chet = 0x0ce7, /* U+05D7 HEBREW LETTER HET */ - XK_hebrew_het = 0x0ce7, /* deprecated */ - XK_hebrew_tet = 0x0ce8, /* U+05D8 HEBREW LETTER TET */ - XK_hebrew_teth = 0x0ce8, /* deprecated */ - XK_hebrew_yod = 0x0ce9, /* U+05D9 HEBREW LETTER YOD */ - XK_hebrew_finalkaph = 0x0cea, /* U+05DA HEBREW LETTER FINAL KAF */ - XK_hebrew_kaph = 0x0ceb, /* U+05DB HEBREW LETTER KAF */ - XK_hebrew_lamed = 0x0cec, /* U+05DC HEBREW LETTER LAMED */ - XK_hebrew_finalmem = 0x0ced, /* U+05DD HEBREW LETTER FINAL MEM */ - XK_hebrew_mem = 0x0cee, /* U+05DE HEBREW LETTER MEM */ - XK_hebrew_finalnun = 0x0cef, /* U+05DF HEBREW LETTER FINAL NUN */ - XK_hebrew_nun = 0x0cf0, /* U+05E0 HEBREW LETTER NUN */ - XK_hebrew_samech = 0x0cf1, /* U+05E1 HEBREW LETTER SAMEKH */ - XK_hebrew_samekh = 0x0cf1, /* deprecated */ - XK_hebrew_ayin = 0x0cf2, /* U+05E2 HEBREW LETTER AYIN */ - XK_hebrew_finalpe = 0x0cf3, /* U+05E3 HEBREW LETTER FINAL PE */ - XK_hebrew_pe = 0x0cf4, /* U+05E4 HEBREW LETTER PE */ - XK_hebrew_finalzade = 0x0cf5, /* U+05E5 HEBREW LETTER FINAL TSADI */ - XK_hebrew_finalzadi = 0x0cf5, /* deprecated */ - XK_hebrew_zade = 0x0cf6, /* U+05E6 HEBREW LETTER TSADI */ - XK_hebrew_zadi = 0x0cf6, /* deprecated */ - XK_hebrew_qoph = 0x0cf7, /* U+05E7 HEBREW LETTER QOF */ - XK_hebrew_kuf = 0x0cf7, /* deprecated */ - XK_hebrew_resh = 0x0cf8, /* U+05E8 HEBREW LETTER RESH */ - XK_hebrew_shin = 0x0cf9, /* U+05E9 HEBREW LETTER SHIN */ - XK_hebrew_taw = 0x0cfa, /* U+05EA HEBREW LETTER TAV */ - XK_hebrew_taf = 0x0cfa, /* deprecated */ - XK_Hebrew_switch = 0xff7e, /* Alias for mode_switch */ - XK_Thai_kokai = 0x0da1, /* U+0E01 THAI CHARACTER KO KAI */ - XK_Thai_khokhai = 0x0da2, /* U+0E02 THAI CHARACTER KHO KHAI */ - XK_Thai_khokhuat = 0x0da3, /* U+0E03 THAI CHARACTER KHO KHUAT */ - XK_Thai_khokhwai = 0x0da4, /* U+0E04 THAI CHARACTER KHO KHWAI */ - XK_Thai_khokhon = 0x0da5, /* U+0E05 THAI CHARACTER KHO KHON */ - XK_Thai_khorakhang = 0x0da6, /* U+0E06 THAI CHARACTER KHO RAKHANG */ - XK_Thai_ngongu = 0x0da7, /* U+0E07 THAI CHARACTER NGO NGU */ - XK_Thai_chochan = 0x0da8, /* U+0E08 THAI CHARACTER CHO CHAN */ - XK_Thai_choching = 0x0da9, /* U+0E09 THAI CHARACTER CHO CHING */ - XK_Thai_chochang = 0x0daa, /* U+0E0A THAI CHARACTER CHO CHANG */ - XK_Thai_soso = 0x0dab, /* U+0E0B THAI CHARACTER SO SO */ - XK_Thai_chochoe = 0x0dac, /* U+0E0C THAI CHARACTER CHO CHOE */ - XK_Thai_yoying = 0x0dad, /* U+0E0D THAI CHARACTER YO YING */ - XK_Thai_dochada = 0x0dae, /* U+0E0E THAI CHARACTER DO CHADA */ - XK_Thai_topatak = 0x0daf, /* U+0E0F THAI CHARACTER TO PATAK */ - XK_Thai_thothan = 0x0db0, /* U+0E10 THAI CHARACTER THO THAN */ - XK_Thai_thonangmontho = 0x0db1, /* U+0E11 THAI CHARACTER THO NANGMONTHO */ - XK_Thai_thophuthao = 0x0db2, /* U+0E12 THAI CHARACTER THO PHUTHAO */ - XK_Thai_nonen = 0x0db3, /* U+0E13 THAI CHARACTER NO NEN */ - XK_Thai_dodek = 0x0db4, /* U+0E14 THAI CHARACTER DO DEK */ - XK_Thai_totao = 0x0db5, /* U+0E15 THAI CHARACTER TO TAO */ - XK_Thai_thothung = 0x0db6, /* U+0E16 THAI CHARACTER THO THUNG */ - XK_Thai_thothahan = 0x0db7, /* U+0E17 THAI CHARACTER THO THAHAN */ - XK_Thai_thothong = 0x0db8, /* U+0E18 THAI CHARACTER THO THONG */ - XK_Thai_nonu = 0x0db9, /* U+0E19 THAI CHARACTER NO NU */ - XK_Thai_bobaimai = 0x0dba, /* U+0E1A THAI CHARACTER BO BAIMAI */ - XK_Thai_popla = 0x0dbb, /* U+0E1B THAI CHARACTER PO PLA */ - XK_Thai_phophung = 0x0dbc, /* U+0E1C THAI CHARACTER PHO PHUNG */ - XK_Thai_fofa = 0x0dbd, /* U+0E1D THAI CHARACTER FO FA */ - XK_Thai_phophan = 0x0dbe, /* U+0E1E THAI CHARACTER PHO PHAN */ - XK_Thai_fofan = 0x0dbf, /* U+0E1F THAI CHARACTER FO FAN */ - XK_Thai_phosamphao = 0x0dc0, /* U+0E20 THAI CHARACTER PHO SAMPHAO */ - XK_Thai_moma = 0x0dc1, /* U+0E21 THAI CHARACTER MO MA */ - XK_Thai_yoyak = 0x0dc2, /* U+0E22 THAI CHARACTER YO YAK */ - XK_Thai_rorua = 0x0dc3, /* U+0E23 THAI CHARACTER RO RUA */ - XK_Thai_ru = 0x0dc4, /* U+0E24 THAI CHARACTER RU */ - XK_Thai_loling = 0x0dc5, /* U+0E25 THAI CHARACTER LO LING */ - XK_Thai_lu = 0x0dc6, /* U+0E26 THAI CHARACTER LU */ - XK_Thai_wowaen = 0x0dc7, /* U+0E27 THAI CHARACTER WO WAEN */ - XK_Thai_sosala = 0x0dc8, /* U+0E28 THAI CHARACTER SO SALA */ - XK_Thai_sorusi = 0x0dc9, /* U+0E29 THAI CHARACTER SO RUSI */ - XK_Thai_sosua = 0x0dca, /* U+0E2A THAI CHARACTER SO SUA */ - XK_Thai_hohip = 0x0dcb, /* U+0E2B THAI CHARACTER HO HIP */ - XK_Thai_lochula = 0x0dcc, /* U+0E2C THAI CHARACTER LO CHULA */ - XK_Thai_oang = 0x0dcd, /* U+0E2D THAI CHARACTER O ANG */ - XK_Thai_honokhuk = 0x0dce, /* U+0E2E THAI CHARACTER HO NOKHUK */ - XK_Thai_paiyannoi = 0x0dcf, /* U+0E2F THAI CHARACTER PAIYANNOI */ - XK_Thai_saraa = 0x0dd0, /* U+0E30 THAI CHARACTER SARA A */ - XK_Thai_maihanakat = 0x0dd1, /* U+0E31 THAI CHARACTER MAI HAN-AKAT */ - XK_Thai_saraaa = 0x0dd2, /* U+0E32 THAI CHARACTER SARA AA */ - XK_Thai_saraam = 0x0dd3, /* U+0E33 THAI CHARACTER SARA AM */ - XK_Thai_sarai = 0x0dd4, /* U+0E34 THAI CHARACTER SARA I */ - XK_Thai_saraii = 0x0dd5, /* U+0E35 THAI CHARACTER SARA II */ - XK_Thai_saraue = 0x0dd6, /* U+0E36 THAI CHARACTER SARA UE */ - XK_Thai_sarauee = 0x0dd7, /* U+0E37 THAI CHARACTER SARA UEE */ - XK_Thai_sarau = 0x0dd8, /* U+0E38 THAI CHARACTER SARA U */ - XK_Thai_sarauu = 0x0dd9, /* U+0E39 THAI CHARACTER SARA UU */ - XK_Thai_phinthu = 0x0dda, /* U+0E3A THAI CHARACTER PHINTHU */ - XK_Thai_maihanakat_maitho = 0x0dde, - XK_Thai_baht = 0x0ddf, /* U+0E3F THAI CURRENCY SYMBOL BAHT */ - XK_Thai_sarae = 0x0de0, /* U+0E40 THAI CHARACTER SARA E */ - XK_Thai_saraae = 0x0de1, /* U+0E41 THAI CHARACTER SARA AE */ - XK_Thai_sarao = 0x0de2, /* U+0E42 THAI CHARACTER SARA O */ - XK_Thai_saraaimaimuan = 0x0de3, /* U+0E43 THAI CHARACTER SARA AI MAIMUAN */ - XK_Thai_saraaimaimalai = 0x0de4, /* U+0E44 THAI CHARACTER SARA AI MAIMALAI */ - XK_Thai_lakkhangyao = 0x0de5, /* U+0E45 THAI CHARACTER LAKKHANGYAO */ - XK_Thai_maiyamok = 0x0de6, /* U+0E46 THAI CHARACTER MAIYAMOK */ - XK_Thai_maitaikhu = 0x0de7, /* U+0E47 THAI CHARACTER MAITAIKHU */ - XK_Thai_maiek = 0x0de8, /* U+0E48 THAI CHARACTER MAI EK */ - XK_Thai_maitho = 0x0de9, /* U+0E49 THAI CHARACTER MAI THO */ - XK_Thai_maitri = 0x0dea, /* U+0E4A THAI CHARACTER MAI TRI */ - XK_Thai_maichattawa = 0x0deb, /* U+0E4B THAI CHARACTER MAI CHATTAWA */ - XK_Thai_thanthakhat = 0x0dec, /* U+0E4C THAI CHARACTER THANTHAKHAT */ - XK_Thai_nikhahit = 0x0ded, /* U+0E4D THAI CHARACTER NIKHAHIT */ - XK_Thai_leksun = 0x0df0, /* U+0E50 THAI DIGIT ZERO */ - XK_Thai_leknung = 0x0df1, /* U+0E51 THAI DIGIT ONE */ - XK_Thai_leksong = 0x0df2, /* U+0E52 THAI DIGIT TWO */ - XK_Thai_leksam = 0x0df3, /* U+0E53 THAI DIGIT THREE */ - XK_Thai_leksi = 0x0df4, /* U+0E54 THAI DIGIT FOUR */ - XK_Thai_lekha = 0x0df5, /* U+0E55 THAI DIGIT FIVE */ - XK_Thai_lekhok = 0x0df6, /* U+0E56 THAI DIGIT SIX */ - XK_Thai_lekchet = 0x0df7, /* U+0E57 THAI DIGIT SEVEN */ - XK_Thai_lekpaet = 0x0df8, /* U+0E58 THAI DIGIT EIGHT */ - XK_Thai_lekkao = 0x0df9, /* U+0E59 THAI DIGIT NINE */ - XK_Hangul = 0xff31, /* Hangul start/stop(toggle) */ - XK_Hangul_Start = 0xff32, /* Hangul start */ - XK_Hangul_End = 0xff33, /* Hangul end, English start */ - XK_Hangul_Hanja = 0xff34, /* Start Hangul->Hanja Conversion */ - XK_Hangul_Jamo = 0xff35, /* Hangul Jamo mode */ - XK_Hangul_Romaja = 0xff36, /* Hangul Romaja mode */ - XK_Hangul_Codeinput = 0xff37, /* Hangul code input mode */ - XK_Hangul_Jeonja = 0xff38, /* Jeonja mode */ - XK_Hangul_Banja = 0xff39, /* Banja mode */ - XK_Hangul_PreHanja = 0xff3a, /* Pre Hanja conversion */ - XK_Hangul_PostHanja = 0xff3b, /* Post Hanja conversion */ - XK_Hangul_SingleCandidate = 0xff3c, /* Single candidate */ - XK_Hangul_MultipleCandidate = 0xff3d, /* Multiple candidate */ - XK_Hangul_PreviousCandidate = 0xff3e, /* Previous candidate */ - XK_Hangul_Special = 0xff3f, /* Special symbols */ - XK_Hangul_switch = 0xff7e, /* Alias for mode_switch */ - XK_Hangul_Kiyeog = 0x0ea1, - XK_Hangul_SsangKiyeog = 0x0ea2, - XK_Hangul_KiyeogSios = 0x0ea3, - XK_Hangul_Nieun = 0x0ea4, - XK_Hangul_NieunJieuj = 0x0ea5, - XK_Hangul_NieunHieuh = 0x0ea6, - XK_Hangul_Dikeud = 0x0ea7, - XK_Hangul_SsangDikeud = 0x0ea8, - XK_Hangul_Rieul = 0x0ea9, - XK_Hangul_RieulKiyeog = 0x0eaa, - XK_Hangul_RieulMieum = 0x0eab, - XK_Hangul_RieulPieub = 0x0eac, - XK_Hangul_RieulSios = 0x0ead, - XK_Hangul_RieulTieut = 0x0eae, - XK_Hangul_RieulPhieuf = 0x0eaf, - XK_Hangul_RieulHieuh = 0x0eb0, - XK_Hangul_Mieum = 0x0eb1, - XK_Hangul_Pieub = 0x0eb2, - XK_Hangul_SsangPieub = 0x0eb3, - XK_Hangul_PieubSios = 0x0eb4, - XK_Hangul_Sios = 0x0eb5, - XK_Hangul_SsangSios = 0x0eb6, - XK_Hangul_Ieung = 0x0eb7, - XK_Hangul_Jieuj = 0x0eb8, - XK_Hangul_SsangJieuj = 0x0eb9, - XK_Hangul_Cieuc = 0x0eba, - XK_Hangul_Khieuq = 0x0ebb, - XK_Hangul_Tieut = 0x0ebc, - XK_Hangul_Phieuf = 0x0ebd, - XK_Hangul_Hieuh = 0x0ebe, - XK_Hangul_A = 0x0ebf, - XK_Hangul_AE = 0x0ec0, - XK_Hangul_YA = 0x0ec1, - XK_Hangul_YAE = 0x0ec2, - XK_Hangul_EO = 0x0ec3, - XK_Hangul_E = 0x0ec4, - XK_Hangul_YEO = 0x0ec5, - XK_Hangul_YE = 0x0ec6, - XK_Hangul_O = 0x0ec7, - XK_Hangul_WA = 0x0ec8, - XK_Hangul_WAE = 0x0ec9, - XK_Hangul_OE = 0x0eca, - XK_Hangul_YO = 0x0ecb, - XK_Hangul_U = 0x0ecc, - XK_Hangul_WEO = 0x0ecd, - XK_Hangul_WE = 0x0ece, - XK_Hangul_WI = 0x0ecf, - XK_Hangul_YU = 0x0ed0, - XK_Hangul_EU = 0x0ed1, - XK_Hangul_YI = 0x0ed2, - XK_Hangul_I = 0x0ed3, - XK_Hangul_J_Kiyeog = 0x0ed4, - XK_Hangul_J_SsangKiyeog = 0x0ed5, - XK_Hangul_J_KiyeogSios = 0x0ed6, - XK_Hangul_J_Nieun = 0x0ed7, - XK_Hangul_J_NieunJieuj = 0x0ed8, - XK_Hangul_J_NieunHieuh = 0x0ed9, - XK_Hangul_J_Dikeud = 0x0eda, - XK_Hangul_J_Rieul = 0x0edb, - XK_Hangul_J_RieulKiyeog = 0x0edc, - XK_Hangul_J_RieulMieum = 0x0edd, - XK_Hangul_J_RieulPieub = 0x0ede, - XK_Hangul_J_RieulSios = 0x0edf, - XK_Hangul_J_RieulTieut = 0x0ee0, - XK_Hangul_J_RieulPhieuf = 0x0ee1, - XK_Hangul_J_RieulHieuh = 0x0ee2, - XK_Hangul_J_Mieum = 0x0ee3, - XK_Hangul_J_Pieub = 0x0ee4, - XK_Hangul_J_PieubSios = 0x0ee5, - XK_Hangul_J_Sios = 0x0ee6, - XK_Hangul_J_SsangSios = 0x0ee7, - XK_Hangul_J_Ieung = 0x0ee8, - XK_Hangul_J_Jieuj = 0x0ee9, - XK_Hangul_J_Cieuc = 0x0eea, - XK_Hangul_J_Khieuq = 0x0eeb, - XK_Hangul_J_Tieut = 0x0eec, - XK_Hangul_J_Phieuf = 0x0eed, - XK_Hangul_J_Hieuh = 0x0eee, - XK_Hangul_RieulYeorinHieuh = 0x0eef, - XK_Hangul_SunkyeongeumMieum = 0x0ef0, - XK_Hangul_SunkyeongeumPieub = 0x0ef1, - XK_Hangul_PanSios = 0x0ef2, - XK_Hangul_KkogjiDalrinIeung = 0x0ef3, - XK_Hangul_SunkyeongeumPhieuf = 0x0ef4, - XK_Hangul_YeorinHieuh = 0x0ef5, - XK_Hangul_AraeA = 0x0ef6, - XK_Hangul_AraeAE = 0x0ef7, - XK_Hangul_J_PanSios = 0x0ef8, - XK_Hangul_J_KkogjiDalrinIeung = 0x0ef9, - XK_Hangul_J_YeorinHieuh = 0x0efa, - XK_Korean_Won = 0x0eff, /*(U+20A9 WON SIGN)*/ - XK_Armenian_ligature_ew = 0x1000587, /* U+0587 ARMENIAN SMALL LIGATURE ECH YIWN */ - XK_Armenian_full_stop = 0x1000589, /* U+0589 ARMENIAN FULL STOP */ - XK_Armenian_verjaket = 0x1000589, /* U+0589 ARMENIAN FULL STOP */ - XK_Armenian_separation_mark = 0x100055d, /* U+055D ARMENIAN COMMA */ - XK_Armenian_but = 0x100055d, /* U+055D ARMENIAN COMMA */ - XK_Armenian_hyphen = 0x100058a, /* U+058A ARMENIAN HYPHEN */ - XK_Armenian_yentamna = 0x100058a, /* U+058A ARMENIAN HYPHEN */ - XK_Armenian_exclam = 0x100055c, /* U+055C ARMENIAN EXCLAMATION MARK */ - XK_Armenian_amanak = 0x100055c, /* U+055C ARMENIAN EXCLAMATION MARK */ - XK_Armenian_accent = 0x100055b, /* U+055B ARMENIAN EMPHASIS MARK */ - XK_Armenian_shesht = 0x100055b, /* U+055B ARMENIAN EMPHASIS MARK */ - XK_Armenian_question = 0x100055e, /* U+055E ARMENIAN QUESTION MARK */ - XK_Armenian_paruyk = 0x100055e, /* U+055E ARMENIAN QUESTION MARK */ - XK_Armenian_AYB = 0x1000531, /* U+0531 ARMENIAN CAPITAL LETTER AYB */ - XK_Armenian_ayb = 0x1000561, /* U+0561 ARMENIAN SMALL LETTER AYB */ - XK_Armenian_BEN = 0x1000532, /* U+0532 ARMENIAN CAPITAL LETTER BEN */ - XK_Armenian_ben = 0x1000562, /* U+0562 ARMENIAN SMALL LETTER BEN */ - XK_Armenian_GIM = 0x1000533, /* U+0533 ARMENIAN CAPITAL LETTER GIM */ - XK_Armenian_gim = 0x1000563, /* U+0563 ARMENIAN SMALL LETTER GIM */ - XK_Armenian_DA = 0x1000534, /* U+0534 ARMENIAN CAPITAL LETTER DA */ - XK_Armenian_da = 0x1000564, /* U+0564 ARMENIAN SMALL LETTER DA */ - XK_Armenian_YECH = 0x1000535, /* U+0535 ARMENIAN CAPITAL LETTER ECH */ - XK_Armenian_yech = 0x1000565, /* U+0565 ARMENIAN SMALL LETTER ECH */ - XK_Armenian_ZA = 0x1000536, /* U+0536 ARMENIAN CAPITAL LETTER ZA */ - XK_Armenian_za = 0x1000566, /* U+0566 ARMENIAN SMALL LETTER ZA */ - XK_Armenian_E = 0x1000537, /* U+0537 ARMENIAN CAPITAL LETTER EH */ - XK_Armenian_e = 0x1000567, /* U+0567 ARMENIAN SMALL LETTER EH */ - XK_Armenian_AT = 0x1000538, /* U+0538 ARMENIAN CAPITAL LETTER ET */ - XK_Armenian_at = 0x1000568, /* U+0568 ARMENIAN SMALL LETTER ET */ - XK_Armenian_TO = 0x1000539, /* U+0539 ARMENIAN CAPITAL LETTER TO */ - XK_Armenian_to = 0x1000569, /* U+0569 ARMENIAN SMALL LETTER TO */ - XK_Armenian_ZHE = 0x100053a, /* U+053A ARMENIAN CAPITAL LETTER ZHE */ - XK_Armenian_zhe = 0x100056a, /* U+056A ARMENIAN SMALL LETTER ZHE */ - XK_Armenian_INI = 0x100053b, /* U+053B ARMENIAN CAPITAL LETTER INI */ - XK_Armenian_ini = 0x100056b, /* U+056B ARMENIAN SMALL LETTER INI */ - XK_Armenian_LYUN = 0x100053c, /* U+053C ARMENIAN CAPITAL LETTER LIWN */ - XK_Armenian_lyun = 0x100056c, /* U+056C ARMENIAN SMALL LETTER LIWN */ - XK_Armenian_KHE = 0x100053d, /* U+053D ARMENIAN CAPITAL LETTER XEH */ - XK_Armenian_khe = 0x100056d, /* U+056D ARMENIAN SMALL LETTER XEH */ - XK_Armenian_TSA = 0x100053e, /* U+053E ARMENIAN CAPITAL LETTER CA */ - XK_Armenian_tsa = 0x100056e, /* U+056E ARMENIAN SMALL LETTER CA */ - XK_Armenian_KEN = 0x100053f, /* U+053F ARMENIAN CAPITAL LETTER KEN */ - XK_Armenian_ken = 0x100056f, /* U+056F ARMENIAN SMALL LETTER KEN */ - XK_Armenian_HO = 0x1000540, /* U+0540 ARMENIAN CAPITAL LETTER HO */ - XK_Armenian_ho = 0x1000570, /* U+0570 ARMENIAN SMALL LETTER HO */ - XK_Armenian_DZA = 0x1000541, /* U+0541 ARMENIAN CAPITAL LETTER JA */ - XK_Armenian_dza = 0x1000571, /* U+0571 ARMENIAN SMALL LETTER JA */ - XK_Armenian_GHAT = 0x1000542, /* U+0542 ARMENIAN CAPITAL LETTER GHAD */ - XK_Armenian_ghat = 0x1000572, /* U+0572 ARMENIAN SMALL LETTER GHAD */ - XK_Armenian_TCHE = 0x1000543, /* U+0543 ARMENIAN CAPITAL LETTER CHEH */ - XK_Armenian_tche = 0x1000573, /* U+0573 ARMENIAN SMALL LETTER CHEH */ - XK_Armenian_MEN = 0x1000544, /* U+0544 ARMENIAN CAPITAL LETTER MEN */ - XK_Armenian_men = 0x1000574, /* U+0574 ARMENIAN SMALL LETTER MEN */ - XK_Armenian_HI = 0x1000545, /* U+0545 ARMENIAN CAPITAL LETTER YI */ - XK_Armenian_hi = 0x1000575, /* U+0575 ARMENIAN SMALL LETTER YI */ - XK_Armenian_NU = 0x1000546, /* U+0546 ARMENIAN CAPITAL LETTER NOW */ - XK_Armenian_nu = 0x1000576, /* U+0576 ARMENIAN SMALL LETTER NOW */ - XK_Armenian_SHA = 0x1000547, /* U+0547 ARMENIAN CAPITAL LETTER SHA */ - XK_Armenian_sha = 0x1000577, /* U+0577 ARMENIAN SMALL LETTER SHA */ - XK_Armenian_VO = 0x1000548, /* U+0548 ARMENIAN CAPITAL LETTER VO */ - XK_Armenian_vo = 0x1000578, /* U+0578 ARMENIAN SMALL LETTER VO */ - XK_Armenian_CHA = 0x1000549, /* U+0549 ARMENIAN CAPITAL LETTER CHA */ - XK_Armenian_cha = 0x1000579, /* U+0579 ARMENIAN SMALL LETTER CHA */ - XK_Armenian_PE = 0x100054a, /* U+054A ARMENIAN CAPITAL LETTER PEH */ - XK_Armenian_pe = 0x100057a, /* U+057A ARMENIAN SMALL LETTER PEH */ - XK_Armenian_JE = 0x100054b, /* U+054B ARMENIAN CAPITAL LETTER JHEH */ - XK_Armenian_je = 0x100057b, /* U+057B ARMENIAN SMALL LETTER JHEH */ - XK_Armenian_RA = 0x100054c, /* U+054C ARMENIAN CAPITAL LETTER RA */ - XK_Armenian_ra = 0x100057c, /* U+057C ARMENIAN SMALL LETTER RA */ - XK_Armenian_SE = 0x100054d, /* U+054D ARMENIAN CAPITAL LETTER SEH */ - XK_Armenian_se = 0x100057d, /* U+057D ARMENIAN SMALL LETTER SEH */ - XK_Armenian_VEV = 0x100054e, /* U+054E ARMENIAN CAPITAL LETTER VEW */ - XK_Armenian_vev = 0x100057e, /* U+057E ARMENIAN SMALL LETTER VEW */ - XK_Armenian_TYUN = 0x100054f, /* U+054F ARMENIAN CAPITAL LETTER TIWN */ - XK_Armenian_tyun = 0x100057f, /* U+057F ARMENIAN SMALL LETTER TIWN */ - XK_Armenian_RE = 0x1000550, /* U+0550 ARMENIAN CAPITAL LETTER REH */ - XK_Armenian_re = 0x1000580, /* U+0580 ARMENIAN SMALL LETTER REH */ - XK_Armenian_TSO = 0x1000551, /* U+0551 ARMENIAN CAPITAL LETTER CO */ - XK_Armenian_tso = 0x1000581, /* U+0581 ARMENIAN SMALL LETTER CO */ - XK_Armenian_VYUN = 0x1000552, /* U+0552 ARMENIAN CAPITAL LETTER YIWN */ - XK_Armenian_vyun = 0x1000582, /* U+0582 ARMENIAN SMALL LETTER YIWN */ - XK_Armenian_PYUR = 0x1000553, /* U+0553 ARMENIAN CAPITAL LETTER PIWR */ - XK_Armenian_pyur = 0x1000583, /* U+0583 ARMENIAN SMALL LETTER PIWR */ - XK_Armenian_KE = 0x1000554, /* U+0554 ARMENIAN CAPITAL LETTER KEH */ - XK_Armenian_ke = 0x1000584, /* U+0584 ARMENIAN SMALL LETTER KEH */ - XK_Armenian_O = 0x1000555, /* U+0555 ARMENIAN CAPITAL LETTER OH */ - XK_Armenian_o = 0x1000585, /* U+0585 ARMENIAN SMALL LETTER OH */ - XK_Armenian_FE = 0x1000556, /* U+0556 ARMENIAN CAPITAL LETTER FEH */ - XK_Armenian_fe = 0x1000586, /* U+0586 ARMENIAN SMALL LETTER FEH */ - XK_Armenian_apostrophe = 0x100055a, /* U+055A ARMENIAN APOSTROPHE */ - XK_Georgian_an = 0x10010d0, /* U+10D0 GEORGIAN LETTER AN */ - XK_Georgian_ban = 0x10010d1, /* U+10D1 GEORGIAN LETTER BAN */ - XK_Georgian_gan = 0x10010d2, /* U+10D2 GEORGIAN LETTER GAN */ - XK_Georgian_don = 0x10010d3, /* U+10D3 GEORGIAN LETTER DON */ - XK_Georgian_en = 0x10010d4, /* U+10D4 GEORGIAN LETTER EN */ - XK_Georgian_vin = 0x10010d5, /* U+10D5 GEORGIAN LETTER VIN */ - XK_Georgian_zen = 0x10010d6, /* U+10D6 GEORGIAN LETTER ZEN */ - XK_Georgian_tan = 0x10010d7, /* U+10D7 GEORGIAN LETTER TAN */ - XK_Georgian_in = 0x10010d8, /* U+10D8 GEORGIAN LETTER IN */ - XK_Georgian_kan = 0x10010d9, /* U+10D9 GEORGIAN LETTER KAN */ - XK_Georgian_las = 0x10010da, /* U+10DA GEORGIAN LETTER LAS */ - XK_Georgian_man = 0x10010db, /* U+10DB GEORGIAN LETTER MAN */ - XK_Georgian_nar = 0x10010dc, /* U+10DC GEORGIAN LETTER NAR */ - XK_Georgian_on = 0x10010dd, /* U+10DD GEORGIAN LETTER ON */ - XK_Georgian_par = 0x10010de, /* U+10DE GEORGIAN LETTER PAR */ - XK_Georgian_zhar = 0x10010df, /* U+10DF GEORGIAN LETTER ZHAR */ - XK_Georgian_rae = 0x10010e0, /* U+10E0 GEORGIAN LETTER RAE */ - XK_Georgian_san = 0x10010e1, /* U+10E1 GEORGIAN LETTER SAN */ - XK_Georgian_tar = 0x10010e2, /* U+10E2 GEORGIAN LETTER TAR */ - XK_Georgian_un = 0x10010e3, /* U+10E3 GEORGIAN LETTER UN */ - XK_Georgian_phar = 0x10010e4, /* U+10E4 GEORGIAN LETTER PHAR */ - XK_Georgian_khar = 0x10010e5, /* U+10E5 GEORGIAN LETTER KHAR */ - XK_Georgian_ghan = 0x10010e6, /* U+10E6 GEORGIAN LETTER GHAN */ - XK_Georgian_qar = 0x10010e7, /* U+10E7 GEORGIAN LETTER QAR */ - XK_Georgian_shin = 0x10010e8, /* U+10E8 GEORGIAN LETTER SHIN */ - XK_Georgian_chin = 0x10010e9, /* U+10E9 GEORGIAN LETTER CHIN */ - XK_Georgian_can = 0x10010ea, /* U+10EA GEORGIAN LETTER CAN */ - XK_Georgian_jil = 0x10010eb, /* U+10EB GEORGIAN LETTER JIL */ - XK_Georgian_cil = 0x10010ec, /* U+10EC GEORGIAN LETTER CIL */ - XK_Georgian_char = 0x10010ed, /* U+10ED GEORGIAN LETTER CHAR */ - XK_Georgian_xan = 0x10010ee, /* U+10EE GEORGIAN LETTER XAN */ - XK_Georgian_jhan = 0x10010ef, /* U+10EF GEORGIAN LETTER JHAN */ - XK_Georgian_hae = 0x10010f0, /* U+10F0 GEORGIAN LETTER HAE */ - XK_Georgian_he = 0x10010f1, /* U+10F1 GEORGIAN LETTER HE */ - XK_Georgian_hie = 0x10010f2, /* U+10F2 GEORGIAN LETTER HIE */ - XK_Georgian_we = 0x10010f3, /* U+10F3 GEORGIAN LETTER WE */ - XK_Georgian_har = 0x10010f4, /* U+10F4 GEORGIAN LETTER HAR */ - XK_Georgian_hoe = 0x10010f5, /* U+10F5 GEORGIAN LETTER HOE */ - XK_Georgian_fi = 0x10010f6, /* U+10F6 GEORGIAN LETTER FI */ - XK_Xabovedot = 0x1001e8a, /* U+1E8A LATIN CAPITAL LETTER X WITH DOT ABOVE */ - XK_Ibreve = 0x100012c, /* U+012C LATIN CAPITAL LETTER I WITH BREVE */ - XK_Zstroke = 0x10001b5, /* U+01B5 LATIN CAPITAL LETTER Z WITH STROKE */ - XK_Gcaron = 0x10001e6, /* U+01E6 LATIN CAPITAL LETTER G WITH CARON */ - XK_Ocaron = 0x10001d1, /* U+01D2 LATIN CAPITAL LETTER O WITH CARON */ - XK_Obarred = 0x100019f, /* U+019F LATIN CAPITAL LETTER O WITH MIDDLE TILDE */ - XK_xabovedot = 0x1001e8b, /* U+1E8B LATIN SMALL LETTER X WITH DOT ABOVE */ - XK_ibreve = 0x100012d, /* U+012D LATIN SMALL LETTER I WITH BREVE */ - XK_zstroke = 0x10001b6, /* U+01B6 LATIN SMALL LETTER Z WITH STROKE */ - XK_gcaron = 0x10001e7, /* U+01E7 LATIN SMALL LETTER G WITH CARON */ - XK_ocaron = 0x10001d2, /* U+01D2 LATIN SMALL LETTER O WITH CARON */ - XK_obarred = 0x1000275, /* U+0275 LATIN SMALL LETTER BARRED O */ - XK_SCHWA = 0x100018f, /* U+018F LATIN CAPITAL LETTER SCHWA */ - XK_schwa = 0x1000259, /* U+0259 LATIN SMALL LETTER SCHWA */ - XK_Lbelowdot = 0x1001e36, /* U+1E36 LATIN CAPITAL LETTER L WITH DOT BELOW */ - XK_lbelowdot = 0x1001e37, /* U+1E37 LATIN SMALL LETTER L WITH DOT BELOW */ - XK_Abelowdot = 0x1001ea0, /* U+1EA0 LATIN CAPITAL LETTER A WITH DOT BELOW */ - XK_abelowdot = 0x1001ea1, /* U+1EA1 LATIN SMALL LETTER A WITH DOT BELOW */ - XK_Ahook = 0x1001ea2, /* U+1EA2 LATIN CAPITAL LETTER A WITH HOOK ABOVE */ - XK_ahook = 0x1001ea3, /* U+1EA3 LATIN SMALL LETTER A WITH HOOK ABOVE */ - XK_Acircumflexacute = 0x1001ea4, /* U+1EA4 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE */ - XK_acircumflexacute = 0x1001ea5, /* U+1EA5 LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE */ - XK_Acircumflexgrave = 0x1001ea6, /* U+1EA6 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE */ - XK_acircumflexgrave = 0x1001ea7, /* U+1EA7 LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE */ - XK_Acircumflexhook = 0x1001ea8, /* U+1EA8 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */ - XK_acircumflexhook = 0x1001ea9, /* U+1EA9 LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */ - XK_Acircumflextilde = 0x1001eaa, /* U+1EAA LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE */ - XK_acircumflextilde = 0x1001eab, /* U+1EAB LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE */ - XK_Acircumflexbelowdot = 0x1001eac, /* U+1EAC LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW */ - XK_acircumflexbelowdot = 0x1001ead, /* U+1EAD LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW */ - XK_Abreveacute = 0x1001eae, /* U+1EAE LATIN CAPITAL LETTER A WITH BREVE AND ACUTE */ - XK_abreveacute = 0x1001eaf, /* U+1EAF LATIN SMALL LETTER A WITH BREVE AND ACUTE */ - XK_Abrevegrave = 0x1001eb0, /* U+1EB0 LATIN CAPITAL LETTER A WITH BREVE AND GRAVE */ - XK_abrevegrave = 0x1001eb1, /* U+1EB1 LATIN SMALL LETTER A WITH BREVE AND GRAVE */ - XK_Abrevehook = 0x1001eb2, /* U+1EB2 LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE */ - XK_abrevehook = 0x1001eb3, /* U+1EB3 LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE */ - XK_Abrevetilde = 0x1001eb4, /* U+1EB4 LATIN CAPITAL LETTER A WITH BREVE AND TILDE */ - XK_abrevetilde = 0x1001eb5, /* U+1EB5 LATIN SMALL LETTER A WITH BREVE AND TILDE */ - XK_Abrevebelowdot = 0x1001eb6, /* U+1EB6 LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW */ - XK_abrevebelowdot = 0x1001eb7, /* U+1EB7 LATIN SMALL LETTER A WITH BREVE AND DOT BELOW */ - XK_Ebelowdot = 0x1001eb8, /* U+1EB8 LATIN CAPITAL LETTER E WITH DOT BELOW */ - XK_ebelowdot = 0x1001eb9, /* U+1EB9 LATIN SMALL LETTER E WITH DOT BELOW */ - XK_Ehook = 0x1001eba, /* U+1EBA LATIN CAPITAL LETTER E WITH HOOK ABOVE */ - XK_ehook = 0x1001ebb, /* U+1EBB LATIN SMALL LETTER E WITH HOOK ABOVE */ - XK_Etilde = 0x1001ebc, /* U+1EBC LATIN CAPITAL LETTER E WITH TILDE */ - XK_etilde = 0x1001ebd, /* U+1EBD LATIN SMALL LETTER E WITH TILDE */ - XK_Ecircumflexacute = 0x1001ebe, /* U+1EBE LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE */ - XK_ecircumflexacute = 0x1001ebf, /* U+1EBF LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE */ - XK_Ecircumflexgrave = 0x1001ec0, /* U+1EC0 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE */ - XK_ecircumflexgrave = 0x1001ec1, /* U+1EC1 LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE */ - XK_Ecircumflexhook = 0x1001ec2, /* U+1EC2 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */ - XK_ecircumflexhook = 0x1001ec3, /* U+1EC3 LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */ - XK_Ecircumflextilde = 0x1001ec4, /* U+1EC4 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE */ - XK_ecircumflextilde = 0x1001ec5, /* U+1EC5 LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE */ - XK_Ecircumflexbelowdot = 0x1001ec6, /* U+1EC6 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW */ - XK_ecircumflexbelowdot = 0x1001ec7, /* U+1EC7 LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW */ - XK_Ihook = 0x1001ec8, /* U+1EC8 LATIN CAPITAL LETTER I WITH HOOK ABOVE */ - XK_ihook = 0x1001ec9, /* U+1EC9 LATIN SMALL LETTER I WITH HOOK ABOVE */ - XK_Ibelowdot = 0x1001eca, /* U+1ECA LATIN CAPITAL LETTER I WITH DOT BELOW */ - XK_ibelowdot = 0x1001ecb, /* U+1ECB LATIN SMALL LETTER I WITH DOT BELOW */ - XK_Obelowdot = 0x1001ecc, /* U+1ECC LATIN CAPITAL LETTER O WITH DOT BELOW */ - XK_obelowdot = 0x1001ecd, /* U+1ECD LATIN SMALL LETTER O WITH DOT BELOW */ - XK_Ohook = 0x1001ece, /* U+1ECE LATIN CAPITAL LETTER O WITH HOOK ABOVE */ - XK_ohook = 0x1001ecf, /* U+1ECF LATIN SMALL LETTER O WITH HOOK ABOVE */ - XK_Ocircumflexacute = 0x1001ed0, /* U+1ED0 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE */ - XK_ocircumflexacute = 0x1001ed1, /* U+1ED1 LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE */ - XK_Ocircumflexgrave = 0x1001ed2, /* U+1ED2 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE */ - XK_ocircumflexgrave = 0x1001ed3, /* U+1ED3 LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE */ - XK_Ocircumflexhook = 0x1001ed4, /* U+1ED4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */ - XK_ocircumflexhook = 0x1001ed5, /* U+1ED5 LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */ - XK_Ocircumflextilde = 0x1001ed6, /* U+1ED6 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE */ - XK_ocircumflextilde = 0x1001ed7, /* U+1ED7 LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE */ - XK_Ocircumflexbelowdot = 0x1001ed8, /* U+1ED8 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW */ - XK_ocircumflexbelowdot = 0x1001ed9, /* U+1ED9 LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW */ - XK_Ohornacute = 0x1001eda, /* U+1EDA LATIN CAPITAL LETTER O WITH HORN AND ACUTE */ - XK_ohornacute = 0x1001edb, /* U+1EDB LATIN SMALL LETTER O WITH HORN AND ACUTE */ - XK_Ohorngrave = 0x1001edc, /* U+1EDC LATIN CAPITAL LETTER O WITH HORN AND GRAVE */ - XK_ohorngrave = 0x1001edd, /* U+1EDD LATIN SMALL LETTER O WITH HORN AND GRAVE */ - XK_Ohornhook = 0x1001ede, /* U+1EDE LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE */ - XK_ohornhook = 0x1001edf, /* U+1EDF LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE */ - XK_Ohorntilde = 0x1001ee0, /* U+1EE0 LATIN CAPITAL LETTER O WITH HORN AND TILDE */ - XK_ohorntilde = 0x1001ee1, /* U+1EE1 LATIN SMALL LETTER O WITH HORN AND TILDE */ - XK_Ohornbelowdot = 0x1001ee2, /* U+1EE2 LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW */ - XK_ohornbelowdot = 0x1001ee3, /* U+1EE3 LATIN SMALL LETTER O WITH HORN AND DOT BELOW */ - XK_Ubelowdot = 0x1001ee4, /* U+1EE4 LATIN CAPITAL LETTER U WITH DOT BELOW */ - XK_ubelowdot = 0x1001ee5, /* U+1EE5 LATIN SMALL LETTER U WITH DOT BELOW */ - XK_Uhook = 0x1001ee6, /* U+1EE6 LATIN CAPITAL LETTER U WITH HOOK ABOVE */ - XK_uhook = 0x1001ee7, /* U+1EE7 LATIN SMALL LETTER U WITH HOOK ABOVE */ - XK_Uhornacute = 0x1001ee8, /* U+1EE8 LATIN CAPITAL LETTER U WITH HORN AND ACUTE */ - XK_uhornacute = 0x1001ee9, /* U+1EE9 LATIN SMALL LETTER U WITH HORN AND ACUTE */ - XK_Uhorngrave = 0x1001eea, /* U+1EEA LATIN CAPITAL LETTER U WITH HORN AND GRAVE */ - XK_uhorngrave = 0x1001eeb, /* U+1EEB LATIN SMALL LETTER U WITH HORN AND GRAVE */ - XK_Uhornhook = 0x1001eec, /* U+1EEC LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE */ - XK_uhornhook = 0x1001eed, /* U+1EED LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE */ - XK_Uhorntilde = 0x1001eee, /* U+1EEE LATIN CAPITAL LETTER U WITH HORN AND TILDE */ - XK_uhorntilde = 0x1001eef, /* U+1EEF LATIN SMALL LETTER U WITH HORN AND TILDE */ - XK_Uhornbelowdot = 0x1001ef0, /* U+1EF0 LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW */ - XK_uhornbelowdot = 0x1001ef1, /* U+1EF1 LATIN SMALL LETTER U WITH HORN AND DOT BELOW */ - XK_Ybelowdot = 0x1001ef4, /* U+1EF4 LATIN CAPITAL LETTER Y WITH DOT BELOW */ - XK_ybelowdot = 0x1001ef5, /* U+1EF5 LATIN SMALL LETTER Y WITH DOT BELOW */ - XK_Yhook = 0x1001ef6, /* U+1EF6 LATIN CAPITAL LETTER Y WITH HOOK ABOVE */ - XK_yhook = 0x1001ef7, /* U+1EF7 LATIN SMALL LETTER Y WITH HOOK ABOVE */ - XK_Ytilde = 0x1001ef8, /* U+1EF8 LATIN CAPITAL LETTER Y WITH TILDE */ - XK_ytilde = 0x1001ef9, /* U+1EF9 LATIN SMALL LETTER Y WITH TILDE */ - XK_Ohorn = 0x10001a0, /* U+01A0 LATIN CAPITAL LETTER O WITH HORN */ - XK_ohorn = 0x10001a1, /* U+01A1 LATIN SMALL LETTER O WITH HORN */ - XK_Uhorn = 0x10001af, /* U+01AF LATIN CAPITAL LETTER U WITH HORN */ - XK_uhorn = 0x10001b0, /* U+01B0 LATIN SMALL LETTER U WITH HORN */ - XK_EcuSign = 0x10020a0, /* U+20A0 EURO-CURRENCY SIGN */ - XK_ColonSign = 0x10020a1, /* U+20A1 COLON SIGN */ - XK_CruzeiroSign = 0x10020a2, /* U+20A2 CRUZEIRO SIGN */ - XK_FFrancSign = 0x10020a3, /* U+20A3 FRENCH FRANC SIGN */ - XK_LiraSign = 0x10020a4, /* U+20A4 LIRA SIGN */ - XK_MillSign = 0x10020a5, /* U+20A5 MILL SIGN */ - XK_NairaSign = 0x10020a6, /* U+20A6 NAIRA SIGN */ - XK_PesetaSign = 0x10020a7, /* U+20A7 PESETA SIGN */ - XK_RupeeSign = 0x10020a8, /* U+20A8 RUPEE SIGN */ - XK_WonSign = 0x10020a9, /* U+20A9 WON SIGN */ - XK_NewSheqelSign = 0x10020aa, /* U+20AA NEW SHEQEL SIGN */ - XK_DongSign = 0x10020ab, /* U+20AB DONG SIGN */ - XK_EuroSign = 0x20ac, /* U+20AC EURO SIGN */ + XK_BackSpace = 0xff08, /* Back space, back char */ + XK_Tab = 0xff09, + XK_Linefeed = 0xff0a, /* Linefeed, LF */ + XK_Clear = 0xff0b, + XK_Return = 0xff0d, /* Return, enter */ + XK_Pause = 0xff13, /* Pause, hold */ + XK_Scroll_Lock = 0xff14, + XK_Sys_Req = 0xff15, + XK_Escape = 0xff1b, + XK_Delete = 0xffff, /* Delete, rubout */ + XK_Multi_key = 0xff20, /* Multi-key character compose */ + XK_Codeinput = 0xff37, + XK_SingleCandidate = 0xff3c, + XK_MultipleCandidate = 0xff3d, + XK_PreviousCandidate = 0xff3e, + XK_Kanji = 0xff21, /* Kanji, Kanji convert */ + XK_Muhenkan = 0xff22, /* Cancel Conversion */ + XK_Henkan_Mode = 0xff23, /* Start/Stop Conversion */ + XK_Henkan = 0xff23, /* Alias for Henkan_Mode */ + XK_Romaji = 0xff24, /* to Romaji */ + XK_Hiragana = 0xff25, /* to Hiragana */ + XK_Katakana = 0xff26, /* to Katakana */ + XK_Hiragana_Katakana = 0xff27, /* Hiragana/Katakana toggle */ + XK_Zenkaku = 0xff28, /* to Zenkaku */ + XK_Hankaku = 0xff29, /* to Hankaku */ + XK_Zenkaku_Hankaku = 0xff2a, /* Zenkaku/Hankaku toggle */ + XK_Touroku = 0xff2b, /* Add to Dictionary */ + XK_Massyo = 0xff2c, /* Delete from Dictionary */ + XK_Kana_Lock = 0xff2d, /* Kana Lock */ + XK_Kana_Shift = 0xff2e, /* Kana Shift */ + XK_Eisu_Shift = 0xff2f, /* Alphanumeric Shift */ + XK_Eisu_toggle = 0xff30, /* Alphanumeric toggle */ + XK_Kanji_Bangou = 0xff37, /* Codeinput */ + XK_Zen_Koho = 0xff3d, /* Multiple/All Candidate(s) */ + XK_Mae_Koho = 0xff3e, /* Previous Candidate */ + XK_Home = 0xff50, + XK_Left = 0xff51, /* Move left, left arrow */ + XK_Up = 0xff52, /* Move up, up arrow */ + XK_Right = 0xff53, /* Move right, right arrow */ + XK_Down = 0xff54, /* Move down, down arrow */ + XK_Prior = 0xff55, /* Prior, previous */ + XK_Page_Up = 0xff55, + XK_Next = 0xff56, /* Next */ + XK_Page_Down = 0xff56, + XK_End = 0xff57, /* EOL */ + XK_Begin = 0xff58, /* BOL */ + XK_Select = 0xff60, /* Select, mark */ + XK_Print = 0xff61, + XK_Execute = 0xff62, /* Execute, run, do */ + XK_Insert = 0xff63, /* Insert, insert here */ + XK_Undo = 0xff65, + XK_Redo = 0xff66, /* Redo, again */ + XK_Menu = 0xff67, + XK_Find = 0xff68, /* Find, search */ + XK_Cancel = 0xff69, /* Cancel, stop, abort, exit */ + XK_Help = 0xff6a, /* Help */ + XK_Break = 0xff6b, + XK_Mode_switch = 0xff7e, /* Character set switch */ + XK_script_switch = 0xff7e, /* Alias for mode_switch */ + XK_Num_Lock = 0xff7f, + XK_KP_Space = 0xff80, /* Space */ + XK_KP_Tab = 0xff89, + XK_KP_Enter = 0xff8d, /* Enter */ + XK_KP_F1 = 0xff91, /* PF1, KP_A, ... */ + XK_KP_F2 = 0xff92, + XK_KP_F3 = 0xff93, + XK_KP_F4 = 0xff94, + XK_KP_Home = 0xff95, + XK_KP_Left = 0xff96, + XK_KP_Up = 0xff97, + XK_KP_Right = 0xff98, + XK_KP_Down = 0xff99, + XK_KP_Prior = 0xff9a, + XK_KP_Page_Up = 0xff9a, + XK_KP_Next = 0xff9b, + XK_KP_Page_Down = 0xff9b, + XK_KP_End = 0xff9c, + XK_KP_Begin = 0xff9d, + XK_KP_Insert = 0xff9e, + XK_KP_Delete = 0xff9f, + XK_KP_Equal = 0xffbd, /* Equals */ + XK_KP_Multiply = 0xffaa, + XK_KP_Add = 0xffab, + XK_KP_Separator = 0xffac, /* Separator, often comma */ + XK_KP_Subtract = 0xffad, + XK_KP_Decimal = 0xffae, + XK_KP_Divide = 0xffaf, + XK_KP_0 = 0xffb0, + XK_KP_1 = 0xffb1, + XK_KP_2 = 0xffb2, + XK_KP_3 = 0xffb3, + XK_KP_4 = 0xffb4, + XK_KP_5 = 0xffb5, + XK_KP_6 = 0xffb6, + XK_KP_7 = 0xffb7, + XK_KP_8 = 0xffb8, + XK_KP_9 = 0xffb9, + XK_F1 = 0xffbe, + XK_F2 = 0xffbf, + XK_F3 = 0xffc0, + XK_F4 = 0xffc1, + XK_F5 = 0xffc2, + XK_F6 = 0xffc3, + XK_F7 = 0xffc4, + XK_F8 = 0xffc5, + XK_F9 = 0xffc6, + XK_F10 = 0xffc7, + XK_F11 = 0xffc8, + XK_L1 = 0xffc8, + XK_F12 = 0xffc9, + XK_L2 = 0xffc9, + XK_F13 = 0xffca, + XK_L3 = 0xffca, + XK_F14 = 0xffcb, + XK_L4 = 0xffcb, + XK_F15 = 0xffcc, + XK_L5 = 0xffcc, + XK_F16 = 0xffcd, + XK_L6 = 0xffcd, + XK_F17 = 0xffce, + XK_L7 = 0xffce, + XK_F18 = 0xffcf, + XK_L8 = 0xffcf, + XK_F19 = 0xffd0, + XK_L9 = 0xffd0, + XK_F20 = 0xffd1, + XK_L10 = 0xffd1, + XK_F21 = 0xffd2, + XK_R1 = 0xffd2, + XK_F22 = 0xffd3, + XK_R2 = 0xffd3, + XK_F23 = 0xffd4, + XK_R3 = 0xffd4, + XK_F24 = 0xffd5, + XK_R4 = 0xffd5, + XK_F25 = 0xffd6, + XK_R5 = 0xffd6, + XK_F26 = 0xffd7, + XK_R6 = 0xffd7, + XK_F27 = 0xffd8, + XK_R7 = 0xffd8, + XK_F28 = 0xffd9, + XK_R8 = 0xffd9, + XK_F29 = 0xffda, + XK_R9 = 0xffda, + XK_F30 = 0xffdb, + XK_R10 = 0xffdb, + XK_F31 = 0xffdc, + XK_R11 = 0xffdc, + XK_F32 = 0xffdd, + XK_R12 = 0xffdd, + XK_F33 = 0xffde, + XK_R13 = 0xffde, + XK_F34 = 0xffdf, + XK_R14 = 0xffdf, + XK_F35 = 0xffe0, + XK_R15 = 0xffe0, + XK_Shift_L = 0xffe1, /* Left shift */ + XK_Shift_R = 0xffe2, /* Right shift */ + XK_Control_L = 0xffe3, /* Left control */ + XK_Control_R = 0xffe4, /* Right control */ + XK_Caps_Lock = 0xffe5, /* Caps lock */ + XK_Shift_Lock = 0xffe6, /* Shift lock */ + XK_Meta_L = 0xffe7, /* Left meta */ + XK_Meta_R = 0xffe8, /* Right meta */ + XK_Alt_L = 0xffe9, /* Left alt */ + XK_Alt_R = 0xffea, /* Right alt */ + XK_Super_L = 0xffeb, /* Left super */ + XK_Super_R = 0xffec, /* Right super */ + XK_Hyper_L = 0xffed, /* Left hyper */ + XK_Hyper_R = 0xffee, /* Right hyper */ + XK_ISO_Lock = 0xfe01, + XK_ISO_Level2_Latch = 0xfe02, + XK_ISO_Level3_Shift = 0xfe03, + XK_ISO_Level3_Latch = 0xfe04, + XK_ISO_Level3_Lock = 0xfe05, + XK_ISO_Group_Shift = 0xff7e, /* Alias for mode_switch */ + XK_ISO_Group_Latch = 0xfe06, + XK_ISO_Group_Lock = 0xfe07, + XK_ISO_Next_Group = 0xfe08, + XK_ISO_Next_Group_Lock = 0xfe09, + XK_ISO_Prev_Group = 0xfe0a, + XK_ISO_Prev_Group_Lock = 0xfe0b, + XK_ISO_First_Group = 0xfe0c, + XK_ISO_First_Group_Lock = 0xfe0d, + XK_ISO_Last_Group = 0xfe0e, + XK_ISO_Last_Group_Lock = 0xfe0f, + XK_ISO_Left_Tab = 0xfe20, + XK_ISO_Move_Line_Up = 0xfe21, + XK_ISO_Move_Line_Down = 0xfe22, + XK_ISO_Partial_Line_Up = 0xfe23, + XK_ISO_Partial_Line_Down = 0xfe24, + XK_ISO_Partial_Space_Left = 0xfe25, + XK_ISO_Partial_Space_Right = 0xfe26, + XK_ISO_Set_Margin_Left = 0xfe27, + XK_ISO_Set_Margin_Right = 0xfe28, + XK_ISO_Release_Margin_Left = 0xfe29, + XK_ISO_Release_Margin_Right = 0xfe2a, + XK_ISO_Release_Both_Margins = 0xfe2b, + XK_ISO_Fast_Cursor_Left = 0xfe2c, + XK_ISO_Fast_Cursor_Right = 0xfe2d, + XK_ISO_Fast_Cursor_Up = 0xfe2e, + XK_ISO_Fast_Cursor_Down = 0xfe2f, + XK_ISO_Continuous_Underline = 0xfe30, + XK_ISO_Discontinuous_Underline = 0xfe31, + XK_ISO_Emphasize = 0xfe32, + XK_ISO_Center_Object = 0xfe33, + XK_ISO_Enter = 0xfe34, + XK_dead_grave = 0xfe50, + XK_dead_acute = 0xfe51, + XK_dead_circumflex = 0xfe52, + XK_dead_tilde = 0xfe53, + XK_dead_macron = 0xfe54, + XK_dead_breve = 0xfe55, + XK_dead_abovedot = 0xfe56, + XK_dead_diaeresis = 0xfe57, + XK_dead_abovering = 0xfe58, + XK_dead_doubleacute = 0xfe59, + XK_dead_caron = 0xfe5a, + XK_dead_cedilla = 0xfe5b, + XK_dead_ogonek = 0xfe5c, + XK_dead_iota = 0xfe5d, + XK_dead_voiced_sound = 0xfe5e, + XK_dead_semivoiced_sound = 0xfe5f, + XK_dead_belowdot = 0xfe60, + XK_dead_hook = 0xfe61, + XK_dead_horn = 0xfe62, + XK_First_Virtual_Screen = 0xfed0, + XK_Prev_Virtual_Screen = 0xfed1, + XK_Next_Virtual_Screen = 0xfed2, + XK_Last_Virtual_Screen = 0xfed4, + XK_Terminate_Server = 0xfed5, + XK_AccessX_Enable = 0xfe70, + XK_AccessX_Feedback_Enable = 0xfe71, + XK_RepeatKeys_Enable = 0xfe72, + XK_SlowKeys_Enable = 0xfe73, + XK_BounceKeys_Enable = 0xfe74, + XK_StickyKeys_Enable = 0xfe75, + XK_MouseKeys_Enable = 0xfe76, + XK_MouseKeys_Accel_Enable = 0xfe77, + XK_Overlay1_Enable = 0xfe78, + XK_Overlay2_Enable = 0xfe79, + XK_AudibleBell_Enable = 0xfe7a, + XK_Pointer_Left = 0xfee0, + XK_Pointer_Right = 0xfee1, + XK_Pointer_Up = 0xfee2, + XK_Pointer_Down = 0xfee3, + XK_Pointer_UpLeft = 0xfee4, + XK_Pointer_UpRight = 0xfee5, + XK_Pointer_DownLeft = 0xfee6, + XK_Pointer_DownRight = 0xfee7, + XK_Pointer_Button_Dflt = 0xfee8, + XK_Pointer_Button1 = 0xfee9, + XK_Pointer_Button2 = 0xfeea, + XK_Pointer_Button3 = 0xfeeb, + XK_Pointer_Button4 = 0xfeec, + XK_Pointer_Button5 = 0xfeed, + XK_Pointer_DblClick_Dflt = 0xfeee, + XK_Pointer_DblClick1 = 0xfeef, + XK_Pointer_DblClick2 = 0xfef0, + XK_Pointer_DblClick3 = 0xfef1, + XK_Pointer_DblClick4 = 0xfef2, + XK_Pointer_DblClick5 = 0xfef3, + XK_Pointer_Drag_Dflt = 0xfef4, + XK_Pointer_Drag1 = 0xfef5, + XK_Pointer_Drag2 = 0xfef6, + XK_Pointer_Drag3 = 0xfef7, + XK_Pointer_Drag4 = 0xfef8, + XK_Pointer_Drag5 = 0xfefd, + XK_Pointer_EnableKeys = 0xfef9, + XK_Pointer_Accelerate = 0xfefa, + XK_Pointer_DfltBtnNext = 0xfefb, + XK_Pointer_DfltBtnPrev = 0xfefc, + XK_3270_Duplicate = 0xfd01, + XK_3270_FieldMark = 0xfd02, + XK_3270_Right2 = 0xfd03, + XK_3270_Left2 = 0xfd04, + XK_3270_BackTab = 0xfd05, + XK_3270_EraseEOF = 0xfd06, + XK_3270_EraseInput = 0xfd07, + XK_3270_Reset = 0xfd08, + XK_3270_Quit = 0xfd09, + XK_3270_PA1 = 0xfd0a, + XK_3270_PA2 = 0xfd0b, + XK_3270_PA3 = 0xfd0c, + XK_3270_Test = 0xfd0d, + XK_3270_Attn = 0xfd0e, + XK_3270_CursorBlink = 0xfd0f, + XK_3270_AltCursor = 0xfd10, + XK_3270_KeyClick = 0xfd11, + XK_3270_Jump = 0xfd12, + XK_3270_Ident = 0xfd13, + XK_3270_Rule = 0xfd14, + XK_3270_Copy = 0xfd15, + XK_3270_Play = 0xfd16, + XK_3270_Setup = 0xfd17, + XK_3270_Record = 0xfd18, + XK_3270_ChangeScreen = 0xfd19, + XK_3270_DeleteWord = 0xfd1a, + XK_3270_ExSelect = 0xfd1b, + XK_3270_CursorSelect = 0xfd1c, + XK_3270_PrintScreen = 0xfd1d, + XK_3270_Enter = 0xfd1e, + XK_space = 0x0020, /* U+0020 SPACE */ + XK_exclam = 0x0021, /* U+0021 EXCLAMATION MARK */ + XK_quotedbl = 0x0022, /* U+0022 QUOTATION MARK */ + XK_numbersign = 0x0023, /* U+0023 NUMBER SIGN */ + XK_dollar = 0x0024, /* U+0024 DOLLAR SIGN */ + XK_percent = 0x0025, /* U+0025 PERCENT SIGN */ + XK_ampersand = 0x0026, /* U+0026 AMPERSAND */ + XK_apostrophe = 0x0027, /* U+0027 APOSTROPHE */ + XK_quoteright = 0x0027, /* deprecated */ + XK_parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */ + XK_parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */ + XK_asterisk = 0x002a, /* U+002A ASTERISK */ + XK_plus = 0x002b, /* U+002B PLUS SIGN */ + XK_comma = 0x002c, /* U+002C COMMA */ + XK_minus = 0x002d, /* U+002D HYPHEN-MINUS */ + XK_period = 0x002e, /* U+002E FULL STOP */ + XK_slash = 0x002f, /* U+002F SOLIDUS */ + XK_0 = 0x0030, /* U+0030 DIGIT ZERO */ + XK_1 = 0x0031, /* U+0031 DIGIT ONE */ + XK_2 = 0x0032, /* U+0032 DIGIT TWO */ + XK_3 = 0x0033, /* U+0033 DIGIT THREE */ + XK_4 = 0x0034, /* U+0034 DIGIT FOUR */ + XK_5 = 0x0035, /* U+0035 DIGIT FIVE */ + XK_6 = 0x0036, /* U+0036 DIGIT SIX */ + XK_7 = 0x0037, /* U+0037 DIGIT SEVEN */ + XK_8 = 0x0038, /* U+0038 DIGIT EIGHT */ + XK_9 = 0x0039, /* U+0039 DIGIT NINE */ + XK_colon = 0x003a, /* U+003A COLON */ + XK_semicolon = 0x003b, /* U+003B SEMICOLON */ + XK_less = 0x003c, /* U+003C LESS-THAN SIGN */ + XK_equal = 0x003d, /* U+003D EQUALS SIGN */ + XK_greater = 0x003e, /* U+003E GREATER-THAN SIGN */ + XK_question = 0x003f, /* U+003F QUESTION MARK */ + XK_at = 0x0040, /* U+0040 COMMERCIAL AT */ + XK_A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */ + XK_B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */ + XK_C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */ + XK_D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */ + XK_E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */ + XK_F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */ + XK_G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */ + XK_H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */ + XK_I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */ + XK_J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */ + XK_K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */ + XK_L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */ + XK_M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */ + XK_N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */ + XK_O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */ + XK_P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */ + XK_Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */ + XK_R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */ + XK_S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */ + XK_T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */ + XK_U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */ + XK_V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */ + XK_W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */ + XK_X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */ + XK_Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */ + XK_Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */ + XK_bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */ + XK_backslash = 0x005c, /* U+005C REVERSE SOLIDUS */ + XK_bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */ + XK_asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */ + XK_underscore = 0x005f, /* U+005F LOW LINE */ + XK_grave = 0x0060, /* U+0060 GRAVE ACCENT */ + XK_quoteleft = 0x0060, /* deprecated */ + XK_a = 0x0061, /* U+0061 LATIN SMALL LETTER A */ + XK_b = 0x0062, /* U+0062 LATIN SMALL LETTER B */ + XK_c = 0x0063, /* U+0063 LATIN SMALL LETTER C */ + XK_d = 0x0064, /* U+0064 LATIN SMALL LETTER D */ + XK_e = 0x0065, /* U+0065 LATIN SMALL LETTER E */ + XK_f = 0x0066, /* U+0066 LATIN SMALL LETTER F */ + XK_g = 0x0067, /* U+0067 LATIN SMALL LETTER G */ + XK_h = 0x0068, /* U+0068 LATIN SMALL LETTER H */ + XK_i = 0x0069, /* U+0069 LATIN SMALL LETTER I */ + XK_j = 0x006a, /* U+006A LATIN SMALL LETTER J */ + XK_k = 0x006b, /* U+006B LATIN SMALL LETTER K */ + XK_l = 0x006c, /* U+006C LATIN SMALL LETTER L */ + XK_m = 0x006d, /* U+006D LATIN SMALL LETTER M */ + XK_n = 0x006e, /* U+006E LATIN SMALL LETTER N */ + XK_o = 0x006f, /* U+006F LATIN SMALL LETTER O */ + XK_p = 0x0070, /* U+0070 LATIN SMALL LETTER P */ + XK_q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */ + XK_r = 0x0072, /* U+0072 LATIN SMALL LETTER R */ + XK_s = 0x0073, /* U+0073 LATIN SMALL LETTER S */ + XK_t = 0x0074, /* U+0074 LATIN SMALL LETTER T */ + XK_u = 0x0075, /* U+0075 LATIN SMALL LETTER U */ + XK_v = 0x0076, /* U+0076 LATIN SMALL LETTER V */ + XK_w = 0x0077, /* U+0077 LATIN SMALL LETTER W */ + XK_x = 0x0078, /* U+0078 LATIN SMALL LETTER X */ + XK_y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */ + XK_z = 0x007a, /* U+007A LATIN SMALL LETTER Z */ + XK_braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */ + XK_bar = 0x007c, /* U+007C VERTICAL LINE */ + XK_braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */ + XK_asciitilde = 0x007e, /* U+007E TILDE */ + XK_nobreakspace = 0x00a0, /* U+00A0 NO-BREAK SPACE */ + XK_exclamdown = 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */ + XK_cent = 0x00a2, /* U+00A2 CENT SIGN */ + XK_sterling = 0x00a3, /* U+00A3 POUND SIGN */ + XK_currency = 0x00a4, /* U+00A4 CURRENCY SIGN */ + XK_yen = 0x00a5, /* U+00A5 YEN SIGN */ + XK_brokenbar = 0x00a6, /* U+00A6 BROKEN BAR */ + XK_section = 0x00a7, /* U+00A7 SECTION SIGN */ + XK_diaeresis = 0x00a8, /* U+00A8 DIAERESIS */ + XK_copyright = 0x00a9, /* U+00A9 COPYRIGHT SIGN */ + XK_ordfeminine = 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */ + XK_guillemotleft = 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */ + XK_notsign = 0x00ac, /* U+00AC NOT SIGN */ + XK_hyphen = 0x00ad, /* U+00AD SOFT HYPHEN */ + XK_registered = 0x00ae, /* U+00AE REGISTERED SIGN */ + XK_macron = 0x00af, /* U+00AF MACRON */ + XK_degree = 0x00b0, /* U+00B0 DEGREE SIGN */ + XK_plusminus = 0x00b1, /* U+00B1 PLUS-MINUS SIGN */ + XK_twosuperior = 0x00b2, /* U+00B2 SUPERSCRIPT TWO */ + XK_threesuperior = 0x00b3, /* U+00B3 SUPERSCRIPT THREE */ + XK_acute = 0x00b4, /* U+00B4 ACUTE ACCENT */ + XK_mu = 0x00b5, /* U+00B5 MICRO SIGN */ + XK_paragraph = 0x00b6, /* U+00B6 PILCROW SIGN */ + XK_periodcentered = 0x00b7, /* U+00B7 MIDDLE DOT */ + XK_cedilla = 0x00b8, /* U+00B8 CEDILLA */ + XK_onesuperior = 0x00b9, /* U+00B9 SUPERSCRIPT ONE */ + XK_masculine = 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */ + XK_guillemotright = 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */ + XK_onequarter = 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */ + XK_onehalf = 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */ + XK_threequarters = 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */ + XK_questiondown = 0x00bf, /* U+00BF INVERTED QUESTION MARK */ + XK_Agrave = 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */ + XK_Aacute = 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */ + XK_Acircumflex = 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */ + XK_Atilde = 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */ + XK_Adiaeresis = 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */ + XK_Aring = 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */ + XK_AE = 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */ + XK_Ccedilla = 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */ + XK_Egrave = 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */ + XK_Eacute = 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */ + XK_Ecircumflex = 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */ + XK_Ediaeresis = 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */ + XK_Igrave = 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */ + XK_Iacute = 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */ + XK_Icircumflex = 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */ + XK_Idiaeresis = 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */ + XK_ETH = 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */ + XK_Eth = 0x00d0, /* deprecated */ + XK_Ntilde = 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */ + XK_Ograve = 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */ + XK_Oacute = 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */ + XK_Ocircumflex = 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */ + XK_Otilde = 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */ + XK_Odiaeresis = 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */ + XK_multiply = 0x00d7, /* U+00D7 MULTIPLICATION SIGN */ + XK_Oslash = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */ + XK_Ooblique = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */ + XK_Ugrave = 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */ + XK_Uacute = 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */ + XK_Ucircumflex = 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */ + XK_Udiaeresis = 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */ + XK_Yacute = 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */ + XK_THORN = 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */ + XK_Thorn = 0x00de, /* deprecated */ + XK_ssharp = 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */ + XK_agrave = 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */ + XK_aacute = 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */ + XK_acircumflex = 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */ + XK_atilde = 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */ + XK_adiaeresis = 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */ + XK_aring = 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */ + XK_ae = 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */ + XK_ccedilla = 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */ + XK_egrave = 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */ + XK_eacute = 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */ + XK_ecircumflex = 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */ + XK_ediaeresis = 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */ + XK_igrave = 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */ + XK_iacute = 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */ + XK_icircumflex = 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */ + XK_idiaeresis = 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */ + XK_eth = 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */ + XK_ntilde = 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */ + XK_ograve = 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */ + XK_oacute = 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */ + XK_ocircumflex = 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */ + XK_otilde = 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */ + XK_odiaeresis = 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */ + XK_division = 0x00f7, /* U+00F7 DIVISION SIGN */ + XK_oslash = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */ + XK_ooblique = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */ + XK_ugrave = 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */ + XK_uacute = 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */ + XK_ucircumflex = 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */ + XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */ + XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */ + XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ + XK_ydiaeresis = 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ + XK_Aogonek = 0x01a1, /* U+0104 LATIN CAPITAL LETTER A WITH OGONEK */ + XK_breve = 0x01a2, /* U+02D8 BREVE */ + XK_Lstroke = 0x01a3, /* U+0141 LATIN CAPITAL LETTER L WITH STROKE */ + XK_Lcaron = 0x01a5, /* U+013D LATIN CAPITAL LETTER L WITH CARON */ + XK_Sacute = 0x01a6, /* U+015A LATIN CAPITAL LETTER S WITH ACUTE */ + XK_Scaron = 0x01a9, /* U+0160 LATIN CAPITAL LETTER S WITH CARON */ + XK_Scedilla = 0x01aa, /* U+015E LATIN CAPITAL LETTER S WITH CEDILLA */ + XK_Tcaron = 0x01ab, /* U+0164 LATIN CAPITAL LETTER T WITH CARON */ + XK_Zacute = 0x01ac, /* U+0179 LATIN CAPITAL LETTER Z WITH ACUTE */ + XK_Zcaron = 0x01ae, /* U+017D LATIN CAPITAL LETTER Z WITH CARON */ + XK_Zabovedot = 0x01af, /* U+017B LATIN CAPITAL LETTER Z WITH DOT ABOVE */ + XK_aogonek = 0x01b1, /* U+0105 LATIN SMALL LETTER A WITH OGONEK */ + XK_ogonek = 0x01b2, /* U+02DB OGONEK */ + XK_lstroke = 0x01b3, /* U+0142 LATIN SMALL LETTER L WITH STROKE */ + XK_lcaron = 0x01b5, /* U+013E LATIN SMALL LETTER L WITH CARON */ + XK_sacute = 0x01b6, /* U+015B LATIN SMALL LETTER S WITH ACUTE */ + XK_caron = 0x01b7, /* U+02C7 CARON */ + XK_scaron = 0x01b9, /* U+0161 LATIN SMALL LETTER S WITH CARON */ + XK_scedilla = 0x01ba, /* U+015F LATIN SMALL LETTER S WITH CEDILLA */ + XK_tcaron = 0x01bb, /* U+0165 LATIN SMALL LETTER T WITH CARON */ + XK_zacute = 0x01bc, /* U+017A LATIN SMALL LETTER Z WITH ACUTE */ + XK_doubleacute = 0x01bd, /* U+02DD DOUBLE ACUTE ACCENT */ + XK_zcaron = 0x01be, /* U+017E LATIN SMALL LETTER Z WITH CARON */ + XK_zabovedot = 0x01bf, /* U+017C LATIN SMALL LETTER Z WITH DOT ABOVE */ + XK_Racute = 0x01c0, /* U+0154 LATIN CAPITAL LETTER R WITH ACUTE */ + XK_Abreve = 0x01c3, /* U+0102 LATIN CAPITAL LETTER A WITH BREVE */ + XK_Lacute = 0x01c5, /* U+0139 LATIN CAPITAL LETTER L WITH ACUTE */ + XK_Cacute = 0x01c6, /* U+0106 LATIN CAPITAL LETTER C WITH ACUTE */ + XK_Ccaron = 0x01c8, /* U+010C LATIN CAPITAL LETTER C WITH CARON */ + XK_Eogonek = 0x01ca, /* U+0118 LATIN CAPITAL LETTER E WITH OGONEK */ + XK_Ecaron = 0x01cc, /* U+011A LATIN CAPITAL LETTER E WITH CARON */ + XK_Dcaron = 0x01cf, /* U+010E LATIN CAPITAL LETTER D WITH CARON */ + XK_Dstroke = 0x01d0, /* U+0110 LATIN CAPITAL LETTER D WITH STROKE */ + XK_Nacute = 0x01d1, /* U+0143 LATIN CAPITAL LETTER N WITH ACUTE */ + XK_Ncaron = 0x01d2, /* U+0147 LATIN CAPITAL LETTER N WITH CARON */ + XK_Odoubleacute = 0x01d5, /* U+0150 LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ + XK_Rcaron = 0x01d8, /* U+0158 LATIN CAPITAL LETTER R WITH CARON */ + XK_Uring = 0x01d9, /* U+016E LATIN CAPITAL LETTER U WITH RING ABOVE */ + XK_Udoubleacute = 0x01db, /* U+0170 LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ + XK_Tcedilla = 0x01de, /* U+0162 LATIN CAPITAL LETTER T WITH CEDILLA */ + XK_racute = 0x01e0, /* U+0155 LATIN SMALL LETTER R WITH ACUTE */ + XK_abreve = 0x01e3, /* U+0103 LATIN SMALL LETTER A WITH BREVE */ + XK_lacute = 0x01e5, /* U+013A LATIN SMALL LETTER L WITH ACUTE */ + XK_cacute = 0x01e6, /* U+0107 LATIN SMALL LETTER C WITH ACUTE */ + XK_ccaron = 0x01e8, /* U+010D LATIN SMALL LETTER C WITH CARON */ + XK_eogonek = 0x01ea, /* U+0119 LATIN SMALL LETTER E WITH OGONEK */ + XK_ecaron = 0x01ec, /* U+011B LATIN SMALL LETTER E WITH CARON */ + XK_dcaron = 0x01ef, /* U+010F LATIN SMALL LETTER D WITH CARON */ + XK_dstroke = 0x01f0, /* U+0111 LATIN SMALL LETTER D WITH STROKE */ + XK_nacute = 0x01f1, /* U+0144 LATIN SMALL LETTER N WITH ACUTE */ + XK_ncaron = 0x01f2, /* U+0148 LATIN SMALL LETTER N WITH CARON */ + XK_odoubleacute = 0x01f5, /* U+0151 LATIN SMALL LETTER O WITH DOUBLE ACUTE */ + XK_udoubleacute = 0x01fb, /* U+0171 LATIN SMALL LETTER U WITH DOUBLE ACUTE */ + XK_rcaron = 0x01f8, /* U+0159 LATIN SMALL LETTER R WITH CARON */ + XK_uring = 0x01f9, /* U+016F LATIN SMALL LETTER U WITH RING ABOVE */ + XK_tcedilla = 0x01fe, /* U+0163 LATIN SMALL LETTER T WITH CEDILLA */ + XK_abovedot = 0x01ff, /* U+02D9 DOT ABOVE */ + XK_Hstroke = 0x02a1, /* U+0126 LATIN CAPITAL LETTER H WITH STROKE */ + XK_Hcircumflex = 0x02a6, /* U+0124 LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ + XK_Iabovedot = 0x02a9, /* U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE */ + XK_Gbreve = 0x02ab, /* U+011E LATIN CAPITAL LETTER G WITH BREVE */ + XK_Jcircumflex = 0x02ac, /* U+0134 LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ + XK_hstroke = 0x02b1, /* U+0127 LATIN SMALL LETTER H WITH STROKE */ + XK_hcircumflex = 0x02b6, /* U+0125 LATIN SMALL LETTER H WITH CIRCUMFLEX */ + XK_idotless = 0x02b9, /* U+0131 LATIN SMALL LETTER DOTLESS I */ + XK_gbreve = 0x02bb, /* U+011F LATIN SMALL LETTER G WITH BREVE */ + XK_jcircumflex = 0x02bc, /* U+0135 LATIN SMALL LETTER J WITH CIRCUMFLEX */ + XK_Cabovedot = 0x02c5, /* U+010A LATIN CAPITAL LETTER C WITH DOT ABOVE */ + XK_Ccircumflex = 0x02c6, /* U+0108 LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ + XK_Gabovedot = 0x02d5, /* U+0120 LATIN CAPITAL LETTER G WITH DOT ABOVE */ + XK_Gcircumflex = 0x02d8, /* U+011C LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ + XK_Ubreve = 0x02dd, /* U+016C LATIN CAPITAL LETTER U WITH BREVE */ + XK_Scircumflex = 0x02de, /* U+015C LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ + XK_cabovedot = 0x02e5, /* U+010B LATIN SMALL LETTER C WITH DOT ABOVE */ + XK_ccircumflex = 0x02e6, /* U+0109 LATIN SMALL LETTER C WITH CIRCUMFLEX */ + XK_gabovedot = 0x02f5, /* U+0121 LATIN SMALL LETTER G WITH DOT ABOVE */ + XK_gcircumflex = 0x02f8, /* U+011D LATIN SMALL LETTER G WITH CIRCUMFLEX */ + XK_ubreve = 0x02fd, /* U+016D LATIN SMALL LETTER U WITH BREVE */ + XK_scircumflex = 0x02fe, /* U+015D LATIN SMALL LETTER S WITH CIRCUMFLEX */ + XK_kra = 0x03a2, /* U+0138 LATIN SMALL LETTER KRA */ + XK_kappa = 0x03a2, /* deprecated */ + XK_Rcedilla = 0x03a3, /* U+0156 LATIN CAPITAL LETTER R WITH CEDILLA */ + XK_Itilde = 0x03a5, /* U+0128 LATIN CAPITAL LETTER I WITH TILDE */ + XK_Lcedilla = 0x03a6, /* U+013B LATIN CAPITAL LETTER L WITH CEDILLA */ + XK_Emacron = 0x03aa, /* U+0112 LATIN CAPITAL LETTER E WITH MACRON */ + XK_Gcedilla = 0x03ab, /* U+0122 LATIN CAPITAL LETTER G WITH CEDILLA */ + XK_Tslash = 0x03ac, /* U+0166 LATIN CAPITAL LETTER T WITH STROKE */ + XK_rcedilla = 0x03b3, /* U+0157 LATIN SMALL LETTER R WITH CEDILLA */ + XK_itilde = 0x03b5, /* U+0129 LATIN SMALL LETTER I WITH TILDE */ + XK_lcedilla = 0x03b6, /* U+013C LATIN SMALL LETTER L WITH CEDILLA */ + XK_emacron = 0x03ba, /* U+0113 LATIN SMALL LETTER E WITH MACRON */ + XK_gcedilla = 0x03bb, /* U+0123 LATIN SMALL LETTER G WITH CEDILLA */ + XK_tslash = 0x03bc, /* U+0167 LATIN SMALL LETTER T WITH STROKE */ + XK_ENG = 0x03bd, /* U+014A LATIN CAPITAL LETTER ENG */ + XK_eng = 0x03bf, /* U+014B LATIN SMALL LETTER ENG */ + XK_Amacron = 0x03c0, /* U+0100 LATIN CAPITAL LETTER A WITH MACRON */ + XK_Iogonek = 0x03c7, /* U+012E LATIN CAPITAL LETTER I WITH OGONEK */ + XK_Eabovedot = 0x03cc, /* U+0116 LATIN CAPITAL LETTER E WITH DOT ABOVE */ + XK_Imacron = 0x03cf, /* U+012A LATIN CAPITAL LETTER I WITH MACRON */ + XK_Ncedilla = 0x03d1, /* U+0145 LATIN CAPITAL LETTER N WITH CEDILLA */ + XK_Omacron = 0x03d2, /* U+014C LATIN CAPITAL LETTER O WITH MACRON */ + XK_Kcedilla = 0x03d3, /* U+0136 LATIN CAPITAL LETTER K WITH CEDILLA */ + XK_Uogonek = 0x03d9, /* U+0172 LATIN CAPITAL LETTER U WITH OGONEK */ + XK_Utilde = 0x03dd, /* U+0168 LATIN CAPITAL LETTER U WITH TILDE */ + XK_Umacron = 0x03de, /* U+016A LATIN CAPITAL LETTER U WITH MACRON */ + XK_amacron = 0x03e0, /* U+0101 LATIN SMALL LETTER A WITH MACRON */ + XK_iogonek = 0x03e7, /* U+012F LATIN SMALL LETTER I WITH OGONEK */ + XK_eabovedot = 0x03ec, /* U+0117 LATIN SMALL LETTER E WITH DOT ABOVE */ + XK_imacron = 0x03ef, /* U+012B LATIN SMALL LETTER I WITH MACRON */ + XK_ncedilla = 0x03f1, /* U+0146 LATIN SMALL LETTER N WITH CEDILLA */ + XK_omacron = 0x03f2, /* U+014D LATIN SMALL LETTER O WITH MACRON */ + XK_kcedilla = 0x03f3, /* U+0137 LATIN SMALL LETTER K WITH CEDILLA */ + XK_uogonek = 0x03f9, /* U+0173 LATIN SMALL LETTER U WITH OGONEK */ + XK_utilde = 0x03fd, /* U+0169 LATIN SMALL LETTER U WITH TILDE */ + XK_umacron = 0x03fe, /* U+016B LATIN SMALL LETTER U WITH MACRON */ + XK_Babovedot = 0x1001e02, /* U+1E02 LATIN CAPITAL LETTER B WITH DOT ABOVE */ + XK_babovedot = 0x1001e03, /* U+1E03 LATIN SMALL LETTER B WITH DOT ABOVE */ + XK_Dabovedot = 0x1001e0a, /* U+1E0A LATIN CAPITAL LETTER D WITH DOT ABOVE */ + XK_Wgrave = 0x1001e80, /* U+1E80 LATIN CAPITAL LETTER W WITH GRAVE */ + XK_Wacute = 0x1001e82, /* U+1E82 LATIN CAPITAL LETTER W WITH ACUTE */ + XK_dabovedot = 0x1001e0b, /* U+1E0B LATIN SMALL LETTER D WITH DOT ABOVE */ + XK_Ygrave = 0x1001ef2, /* U+1EF2 LATIN CAPITAL LETTER Y WITH GRAVE */ + XK_Fabovedot = 0x1001e1e, /* U+1E1E LATIN CAPITAL LETTER F WITH DOT ABOVE */ + XK_fabovedot = 0x1001e1f, /* U+1E1F LATIN SMALL LETTER F WITH DOT ABOVE */ + XK_Mabovedot = 0x1001e40, /* U+1E40 LATIN CAPITAL LETTER M WITH DOT ABOVE */ + XK_mabovedot = 0x1001e41, /* U+1E41 LATIN SMALL LETTER M WITH DOT ABOVE */ + XK_Pabovedot = 0x1001e56, /* U+1E56 LATIN CAPITAL LETTER P WITH DOT ABOVE */ + XK_wgrave = 0x1001e81, /* U+1E81 LATIN SMALL LETTER W WITH GRAVE */ + XK_pabovedot = 0x1001e57, /* U+1E57 LATIN SMALL LETTER P WITH DOT ABOVE */ + XK_wacute = 0x1001e83, /* U+1E83 LATIN SMALL LETTER W WITH ACUTE */ + XK_Sabovedot = 0x1001e60, /* U+1E60 LATIN CAPITAL LETTER S WITH DOT ABOVE */ + XK_ygrave = 0x1001ef3, /* U+1EF3 LATIN SMALL LETTER Y WITH GRAVE */ + XK_Wdiaeresis = 0x1001e84, /* U+1E84 LATIN CAPITAL LETTER W WITH DIAERESIS */ + XK_wdiaeresis = 0x1001e85, /* U+1E85 LATIN SMALL LETTER W WITH DIAERESIS */ + XK_sabovedot = 0x1001e61, /* U+1E61 LATIN SMALL LETTER S WITH DOT ABOVE */ + XK_Wcircumflex = 0x1000174, /* U+0174 LATIN CAPITAL LETTER W WITH CIRCUMFLEX */ + XK_Tabovedot = 0x1001e6a, /* U+1E6A LATIN CAPITAL LETTER T WITH DOT ABOVE */ + XK_Ycircumflex = 0x1000176, /* U+0176 LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */ + XK_wcircumflex = 0x1000175, /* U+0175 LATIN SMALL LETTER W WITH CIRCUMFLEX */ + XK_tabovedot = 0x1001e6b, /* U+1E6B LATIN SMALL LETTER T WITH DOT ABOVE */ + XK_ycircumflex = 0x1000177, /* U+0177 LATIN SMALL LETTER Y WITH CIRCUMFLEX */ + XK_OE = 0x13bc, /* U+0152 LATIN CAPITAL LIGATURE OE */ + XK_oe = 0x13bd, /* U+0153 LATIN SMALL LIGATURE OE */ + XK_Ydiaeresis = 0x13be, /* U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS */ + XK_overline = 0x047e, /* U+203E OVERLINE */ + XK_kana_fullstop = 0x04a1, /* U+3002 IDEOGRAPHIC FULL STOP */ + XK_kana_openingbracket = 0x04a2, /* U+300C LEFT CORNER BRACKET */ + XK_kana_closingbracket = 0x04a3, /* U+300D RIGHT CORNER BRACKET */ + XK_kana_comma = 0x04a4, /* U+3001 IDEOGRAPHIC COMMA */ + XK_kana_conjunctive = 0x04a5, /* U+30FB KATAKANA MIDDLE DOT */ + XK_kana_middledot = 0x04a5, /* deprecated */ + XK_kana_WO = 0x04a6, /* U+30F2 KATAKANA LETTER WO */ + XK_kana_a = 0x04a7, /* U+30A1 KATAKANA LETTER SMALL A */ + XK_kana_i = 0x04a8, /* U+30A3 KATAKANA LETTER SMALL I */ + XK_kana_u = 0x04a9, /* U+30A5 KATAKANA LETTER SMALL U */ + XK_kana_e = 0x04aa, /* U+30A7 KATAKANA LETTER SMALL E */ + XK_kana_o = 0x04ab, /* U+30A9 KATAKANA LETTER SMALL O */ + XK_kana_ya = 0x04ac, /* U+30E3 KATAKANA LETTER SMALL YA */ + XK_kana_yu = 0x04ad, /* U+30E5 KATAKANA LETTER SMALL YU */ + XK_kana_yo = 0x04ae, /* U+30E7 KATAKANA LETTER SMALL YO */ + XK_kana_tsu = 0x04af, /* U+30C3 KATAKANA LETTER SMALL TU */ + XK_kana_tu = 0x04af, /* deprecated */ + XK_prolongedsound = 0x04b0, /* U+30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK */ + XK_kana_A = 0x04b1, /* U+30A2 KATAKANA LETTER A */ + XK_kana_I = 0x04b2, /* U+30A4 KATAKANA LETTER I */ + XK_kana_U = 0x04b3, /* U+30A6 KATAKANA LETTER U */ + XK_kana_E = 0x04b4, /* U+30A8 KATAKANA LETTER E */ + XK_kana_O = 0x04b5, /* U+30AA KATAKANA LETTER O */ + XK_kana_KA = 0x04b6, /* U+30AB KATAKANA LETTER KA */ + XK_kana_KI = 0x04b7, /* U+30AD KATAKANA LETTER KI */ + XK_kana_KU = 0x04b8, /* U+30AF KATAKANA LETTER KU */ + XK_kana_KE = 0x04b9, /* U+30B1 KATAKANA LETTER KE */ + XK_kana_KO = 0x04ba, /* U+30B3 KATAKANA LETTER KO */ + XK_kana_SA = 0x04bb, /* U+30B5 KATAKANA LETTER SA */ + XK_kana_SHI = 0x04bc, /* U+30B7 KATAKANA LETTER SI */ + XK_kana_SU = 0x04bd, /* U+30B9 KATAKANA LETTER SU */ + XK_kana_SE = 0x04be, /* U+30BB KATAKANA LETTER SE */ + XK_kana_SO = 0x04bf, /* U+30BD KATAKANA LETTER SO */ + XK_kana_TA = 0x04c0, /* U+30BF KATAKANA LETTER TA */ + XK_kana_CHI = 0x04c1, /* U+30C1 KATAKANA LETTER TI */ + XK_kana_TI = 0x04c1, /* deprecated */ + XK_kana_TSU = 0x04c2, /* U+30C4 KATAKANA LETTER TU */ + XK_kana_TU = 0x04c2, /* deprecated */ + XK_kana_TE = 0x04c3, /* U+30C6 KATAKANA LETTER TE */ + XK_kana_TO = 0x04c4, /* U+30C8 KATAKANA LETTER TO */ + XK_kana_NA = 0x04c5, /* U+30CA KATAKANA LETTER NA */ + XK_kana_NI = 0x04c6, /* U+30CB KATAKANA LETTER NI */ + XK_kana_NU = 0x04c7, /* U+30CC KATAKANA LETTER NU */ + XK_kana_NE = 0x04c8, /* U+30CD KATAKANA LETTER NE */ + XK_kana_NO = 0x04c9, /* U+30CE KATAKANA LETTER NO */ + XK_kana_HA = 0x04ca, /* U+30CF KATAKANA LETTER HA */ + XK_kana_HI = 0x04cb, /* U+30D2 KATAKANA LETTER HI */ + XK_kana_FU = 0x04cc, /* U+30D5 KATAKANA LETTER HU */ + XK_kana_HU = 0x04cc, /* deprecated */ + XK_kana_HE = 0x04cd, /* U+30D8 KATAKANA LETTER HE */ + XK_kana_HO = 0x04ce, /* U+30DB KATAKANA LETTER HO */ + XK_kana_MA = 0x04cf, /* U+30DE KATAKANA LETTER MA */ + XK_kana_MI = 0x04d0, /* U+30DF KATAKANA LETTER MI */ + XK_kana_MU = 0x04d1, /* U+30E0 KATAKANA LETTER MU */ + XK_kana_ME = 0x04d2, /* U+30E1 KATAKANA LETTER ME */ + XK_kana_MO = 0x04d3, /* U+30E2 KATAKANA LETTER MO */ + XK_kana_YA = 0x04d4, /* U+30E4 KATAKANA LETTER YA */ + XK_kana_YU = 0x04d5, /* U+30E6 KATAKANA LETTER YU */ + XK_kana_YO = 0x04d6, /* U+30E8 KATAKANA LETTER YO */ + XK_kana_RA = 0x04d7, /* U+30E9 KATAKANA LETTER RA */ + XK_kana_RI = 0x04d8, /* U+30EA KATAKANA LETTER RI */ + XK_kana_RU = 0x04d9, /* U+30EB KATAKANA LETTER RU */ + XK_kana_RE = 0x04da, /* U+30EC KATAKANA LETTER RE */ + XK_kana_RO = 0x04db, /* U+30ED KATAKANA LETTER RO */ + XK_kana_WA = 0x04dc, /* U+30EF KATAKANA LETTER WA */ + XK_kana_N = 0x04dd, /* U+30F3 KATAKANA LETTER N */ + XK_voicedsound = 0x04de, /* U+309B KATAKANA-HIRAGANA VOICED SOUND MARK */ + XK_semivoicedsound = 0x04df, /* U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ + XK_kana_switch = 0xff7e, /* Alias for mode_switch */ + XK_Farsi_0 = 0x10006f0, /* U+06F0 EXTENDED ARABIC-INDIC DIGIT ZERO */ + XK_Farsi_1 = 0x10006f1, /* U+06F1 EXTENDED ARABIC-INDIC DIGIT ONE */ + XK_Farsi_2 = 0x10006f2, /* U+06F2 EXTENDED ARABIC-INDIC DIGIT TWO */ + XK_Farsi_3 = 0x10006f3, /* U+06F3 EXTENDED ARABIC-INDIC DIGIT THREE */ + XK_Farsi_4 = 0x10006f4, /* U+06F4 EXTENDED ARABIC-INDIC DIGIT FOUR */ + XK_Farsi_5 = 0x10006f5, /* U+06F5 EXTENDED ARABIC-INDIC DIGIT FIVE */ + XK_Farsi_6 = 0x10006f6, /* U+06F6 EXTENDED ARABIC-INDIC DIGIT SIX */ + XK_Farsi_7 = 0x10006f7, /* U+06F7 EXTENDED ARABIC-INDIC DIGIT SEVEN */ + XK_Farsi_8 = 0x10006f8, /* U+06F8 EXTENDED ARABIC-INDIC DIGIT EIGHT */ + XK_Farsi_9 = 0x10006f9, /* U+06F9 EXTENDED ARABIC-INDIC DIGIT NINE */ + XK_Arabic_percent = 0x100066a, /* U+066A ARABIC PERCENT SIGN */ + XK_Arabic_superscript_alef = 0x1000670, /* U+0670 ARABIC LETTER SUPERSCRIPT ALEF */ + XK_Arabic_tteh = 0x1000679, /* U+0679 ARABIC LETTER TTEH */ + XK_Arabic_peh = 0x100067e, /* U+067E ARABIC LETTER PEH */ + XK_Arabic_tcheh = 0x1000686, /* U+0686 ARABIC LETTER TCHEH */ + XK_Arabic_ddal = 0x1000688, /* U+0688 ARABIC LETTER DDAL */ + XK_Arabic_rreh = 0x1000691, /* U+0691 ARABIC LETTER RREH */ + XK_Arabic_comma = 0x05ac, /* U+060C ARABIC COMMA */ + XK_Arabic_fullstop = 0x10006d4, /* U+06D4 ARABIC FULL STOP */ + XK_Arabic_0 = 0x1000660, /* U+0660 ARABIC-INDIC DIGIT ZERO */ + XK_Arabic_1 = 0x1000661, /* U+0661 ARABIC-INDIC DIGIT ONE */ + XK_Arabic_2 = 0x1000662, /* U+0662 ARABIC-INDIC DIGIT TWO */ + XK_Arabic_3 = 0x1000663, /* U+0663 ARABIC-INDIC DIGIT THREE */ + XK_Arabic_4 = 0x1000664, /* U+0664 ARABIC-INDIC DIGIT FOUR */ + XK_Arabic_5 = 0x1000665, /* U+0665 ARABIC-INDIC DIGIT FIVE */ + XK_Arabic_6 = 0x1000666, /* U+0666 ARABIC-INDIC DIGIT SIX */ + XK_Arabic_7 = 0x1000667, /* U+0667 ARABIC-INDIC DIGIT SEVEN */ + XK_Arabic_8 = 0x1000668, /* U+0668 ARABIC-INDIC DIGIT EIGHT */ + XK_Arabic_9 = 0x1000669, /* U+0669 ARABIC-INDIC DIGIT NINE */ + XK_Arabic_semicolon = 0x05bb, /* U+061B ARABIC SEMICOLON */ + XK_Arabic_question_mark = 0x05bf, /* U+061F ARABIC QUESTION MARK */ + XK_Arabic_hamza = 0x05c1, /* U+0621 ARABIC LETTER HAMZA */ + XK_Arabic_maddaonalef = 0x05c2, /* U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE */ + XK_Arabic_hamzaonalef = 0x05c3, /* U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE */ + XK_Arabic_hamzaonwaw = 0x05c4, /* U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE */ + XK_Arabic_hamzaunderalef = 0x05c5, /* U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW */ + XK_Arabic_hamzaonyeh = 0x05c6, /* U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE */ + XK_Arabic_alef = 0x05c7, /* U+0627 ARABIC LETTER ALEF */ + XK_Arabic_beh = 0x05c8, /* U+0628 ARABIC LETTER BEH */ + XK_Arabic_tehmarbuta = 0x05c9, /* U+0629 ARABIC LETTER TEH MARBUTA */ + XK_Arabic_teh = 0x05ca, /* U+062A ARABIC LETTER TEH */ + XK_Arabic_theh = 0x05cb, /* U+062B ARABIC LETTER THEH */ + XK_Arabic_jeem = 0x05cc, /* U+062C ARABIC LETTER JEEM */ + XK_Arabic_hah = 0x05cd, /* U+062D ARABIC LETTER HAH */ + XK_Arabic_khah = 0x05ce, /* U+062E ARABIC LETTER KHAH */ + XK_Arabic_dal = 0x05cf, /* U+062F ARABIC LETTER DAL */ + XK_Arabic_thal = 0x05d0, /* U+0630 ARABIC LETTER THAL */ + XK_Arabic_ra = 0x05d1, /* U+0631 ARABIC LETTER REH */ + XK_Arabic_zain = 0x05d2, /* U+0632 ARABIC LETTER ZAIN */ + XK_Arabic_seen = 0x05d3, /* U+0633 ARABIC LETTER SEEN */ + XK_Arabic_sheen = 0x05d4, /* U+0634 ARABIC LETTER SHEEN */ + XK_Arabic_sad = 0x05d5, /* U+0635 ARABIC LETTER SAD */ + XK_Arabic_dad = 0x05d6, /* U+0636 ARABIC LETTER DAD */ + XK_Arabic_tah = 0x05d7, /* U+0637 ARABIC LETTER TAH */ + XK_Arabic_zah = 0x05d8, /* U+0638 ARABIC LETTER ZAH */ + XK_Arabic_ain = 0x05d9, /* U+0639 ARABIC LETTER AIN */ + XK_Arabic_ghain = 0x05da, /* U+063A ARABIC LETTER GHAIN */ + XK_Arabic_tatweel = 0x05e0, /* U+0640 ARABIC TATWEEL */ + XK_Arabic_feh = 0x05e1, /* U+0641 ARABIC LETTER FEH */ + XK_Arabic_qaf = 0x05e2, /* U+0642 ARABIC LETTER QAF */ + XK_Arabic_kaf = 0x05e3, /* U+0643 ARABIC LETTER KAF */ + XK_Arabic_lam = 0x05e4, /* U+0644 ARABIC LETTER LAM */ + XK_Arabic_meem = 0x05e5, /* U+0645 ARABIC LETTER MEEM */ + XK_Arabic_noon = 0x05e6, /* U+0646 ARABIC LETTER NOON */ + XK_Arabic_ha = 0x05e7, /* U+0647 ARABIC LETTER HEH */ + XK_Arabic_heh = 0x05e7, /* deprecated */ + XK_Arabic_waw = 0x05e8, /* U+0648 ARABIC LETTER WAW */ + XK_Arabic_alefmaksura = 0x05e9, /* U+0649 ARABIC LETTER ALEF MAKSURA */ + XK_Arabic_yeh = 0x05ea, /* U+064A ARABIC LETTER YEH */ + XK_Arabic_fathatan = 0x05eb, /* U+064B ARABIC FATHATAN */ + XK_Arabic_dammatan = 0x05ec, /* U+064C ARABIC DAMMATAN */ + XK_Arabic_kasratan = 0x05ed, /* U+064D ARABIC KASRATAN */ + XK_Arabic_fatha = 0x05ee, /* U+064E ARABIC FATHA */ + XK_Arabic_damma = 0x05ef, /* U+064F ARABIC DAMMA */ + XK_Arabic_kasra = 0x05f0, /* U+0650 ARABIC KASRA */ + XK_Arabic_shadda = 0x05f1, /* U+0651 ARABIC SHADDA */ + XK_Arabic_sukun = 0x05f2, /* U+0652 ARABIC SUKUN */ + XK_Arabic_madda_above = 0x1000653, /* U+0653 ARABIC MADDAH ABOVE */ + XK_Arabic_hamza_above = 0x1000654, /* U+0654 ARABIC HAMZA ABOVE */ + XK_Arabic_hamza_below = 0x1000655, /* U+0655 ARABIC HAMZA BELOW */ + XK_Arabic_jeh = 0x1000698, /* U+0698 ARABIC LETTER JEH */ + XK_Arabic_veh = 0x10006a4, /* U+06A4 ARABIC LETTER VEH */ + XK_Arabic_keheh = 0x10006a9, /* U+06A9 ARABIC LETTER KEHEH */ + XK_Arabic_gaf = 0x10006af, /* U+06AF ARABIC LETTER GAF */ + XK_Arabic_noon_ghunna = 0x10006ba, /* U+06BA ARABIC LETTER NOON GHUNNA */ + XK_Arabic_heh_doachashmee = 0x10006be, /* U+06BE ARABIC LETTER HEH DOACHASHMEE */ + XK_Farsi_yeh = 0x10006cc, /* U+06CC ARABIC LETTER FARSI YEH */ + XK_Arabic_farsi_yeh = 0x10006cc, /* U+06CC ARABIC LETTER FARSI YEH */ + XK_Arabic_yeh_baree = 0x10006d2, /* U+06D2 ARABIC LETTER YEH BARREE */ + XK_Arabic_heh_goal = 0x10006c1, /* U+06C1 ARABIC LETTER HEH GOAL */ + XK_Arabic_switch = 0xff7e, /* Alias for mode_switch */ + XK_Cyrillic_GHE_bar = 0x1000492, /* U+0492 CYRILLIC CAPITAL LETTER GHE WITH STROKE */ + XK_Cyrillic_ghe_bar = 0x1000493, /* U+0493 CYRILLIC SMALL LETTER GHE WITH STROKE */ + XK_Cyrillic_ZHE_descender = 0x1000496, /* U+0496 CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER */ + XK_Cyrillic_zhe_descender = 0x1000497, /* U+0497 CYRILLIC SMALL LETTER ZHE WITH DESCENDER */ + XK_Cyrillic_KA_descender = 0x100049a, /* U+049A CYRILLIC CAPITAL LETTER KA WITH DESCENDER */ + XK_Cyrillic_ka_descender = 0x100049b, /* U+049B CYRILLIC SMALL LETTER KA WITH DESCENDER */ + XK_Cyrillic_KA_vertstroke = 0x100049c, /* U+049C CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE */ + XK_Cyrillic_ka_vertstroke = 0x100049d, /* U+049D CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE */ + XK_Cyrillic_EN_descender = 0x10004a2, /* U+04A2 CYRILLIC CAPITAL LETTER EN WITH DESCENDER */ + XK_Cyrillic_en_descender = 0x10004a3, /* U+04A3 CYRILLIC SMALL LETTER EN WITH DESCENDER */ + XK_Cyrillic_U_straight = 0x10004ae, /* U+04AE CYRILLIC CAPITAL LETTER STRAIGHT U */ + XK_Cyrillic_u_straight = 0x10004af, /* U+04AF CYRILLIC SMALL LETTER STRAIGHT U */ + XK_Cyrillic_U_straight_bar = 0x10004b0, /* U+04B0 CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE */ + XK_Cyrillic_u_straight_bar = 0x10004b1, /* U+04B1 CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE */ + XK_Cyrillic_HA_descender = 0x10004b2, /* U+04B2 CYRILLIC CAPITAL LETTER HA WITH DESCENDER */ + XK_Cyrillic_ha_descender = 0x10004b3, /* U+04B3 CYRILLIC SMALL LETTER HA WITH DESCENDER */ + XK_Cyrillic_CHE_descender = 0x10004b6, /* U+04B6 CYRILLIC CAPITAL LETTER CHE WITH DESCENDER */ + XK_Cyrillic_che_descender = 0x10004b7, /* U+04B7 CYRILLIC SMALL LETTER CHE WITH DESCENDER */ + XK_Cyrillic_CHE_vertstroke = 0x10004b8, /* U+04B8 CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE */ + XK_Cyrillic_che_vertstroke = 0x10004b9, /* U+04B9 CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE */ + XK_Cyrillic_SHHA = 0x10004ba, /* U+04BA CYRILLIC CAPITAL LETTER SHHA */ + XK_Cyrillic_shha = 0x10004bb, /* U+04BB CYRILLIC SMALL LETTER SHHA */ + XK_Cyrillic_SCHWA = 0x10004d8, /* U+04D8 CYRILLIC CAPITAL LETTER SCHWA */ + XK_Cyrillic_schwa = 0x10004d9, /* U+04D9 CYRILLIC SMALL LETTER SCHWA */ + XK_Cyrillic_I_macron = 0x10004e2, /* U+04E2 CYRILLIC CAPITAL LETTER I WITH MACRON */ + XK_Cyrillic_i_macron = 0x10004e3, /* U+04E3 CYRILLIC SMALL LETTER I WITH MACRON */ + XK_Cyrillic_O_bar = 0x10004e8, /* U+04E8 CYRILLIC CAPITAL LETTER BARRED O */ + XK_Cyrillic_o_bar = 0x10004e9, /* U+04E9 CYRILLIC SMALL LETTER BARRED O */ + XK_Cyrillic_U_macron = 0x10004ee, /* U+04EE CYRILLIC CAPITAL LETTER U WITH MACRON */ + XK_Cyrillic_u_macron = 0x10004ef, /* U+04EF CYRILLIC SMALL LETTER U WITH MACRON */ + XK_Serbian_dje = 0x06a1, /* U+0452 CYRILLIC SMALL LETTER DJE */ + XK_Macedonia_gje = 0x06a2, /* U+0453 CYRILLIC SMALL LETTER GJE */ + XK_Cyrillic_io = 0x06a3, /* U+0451 CYRILLIC SMALL LETTER IO */ + XK_Ukrainian_ie = 0x06a4, /* U+0454 CYRILLIC SMALL LETTER UKRAINIAN IE */ + XK_Ukranian_je = 0x06a4, /* deprecated */ + XK_Macedonia_dse = 0x06a5, /* U+0455 CYRILLIC SMALL LETTER DZE */ + XK_Ukrainian_i = 0x06a6, /* U+0456 CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ + XK_Ukranian_i = 0x06a6, /* deprecated */ + XK_Ukrainian_yi = 0x06a7, /* U+0457 CYRILLIC SMALL LETTER YI */ + XK_Ukranian_yi = 0x06a7, /* deprecated */ + XK_Cyrillic_je = 0x06a8, /* U+0458 CYRILLIC SMALL LETTER JE */ + XK_Serbian_je = 0x06a8, /* deprecated */ + XK_Cyrillic_lje = 0x06a9, /* U+0459 CYRILLIC SMALL LETTER LJE */ + XK_Serbian_lje = 0x06a9, /* deprecated */ + XK_Cyrillic_nje = 0x06aa, /* U+045A CYRILLIC SMALL LETTER NJE */ + XK_Serbian_nje = 0x06aa, /* deprecated */ + XK_Serbian_tshe = 0x06ab, /* U+045B CYRILLIC SMALL LETTER TSHE */ + XK_Macedonia_kje = 0x06ac, /* U+045C CYRILLIC SMALL LETTER KJE */ + XK_Ukrainian_ghe_with_upturn = 0x06ad, /* U+0491 CYRILLIC SMALL LETTER GHE WITH UPTURN */ + XK_Byelorussian_shortu = 0x06ae, /* U+045E CYRILLIC SMALL LETTER SHORT U */ + XK_Cyrillic_dzhe = 0x06af, /* U+045F CYRILLIC SMALL LETTER DZHE */ + XK_Serbian_dze = 0x06af, /* deprecated */ + XK_numerosign = 0x06b0, /* U+2116 NUMERO SIGN */ + XK_Serbian_DJE = 0x06b1, /* U+0402 CYRILLIC CAPITAL LETTER DJE */ + XK_Macedonia_GJE = 0x06b2, /* U+0403 CYRILLIC CAPITAL LETTER GJE */ + XK_Cyrillic_IO = 0x06b3, /* U+0401 CYRILLIC CAPITAL LETTER IO */ + XK_Ukrainian_IE = 0x06b4, /* U+0404 CYRILLIC CAPITAL LETTER UKRAINIAN IE */ + XK_Ukranian_JE = 0x06b4, /* deprecated */ + XK_Macedonia_DSE = 0x06b5, /* U+0405 CYRILLIC CAPITAL LETTER DZE */ + XK_Ukrainian_I = 0x06b6, /* U+0406 CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ + XK_Ukranian_I = 0x06b6, /* deprecated */ + XK_Ukrainian_YI = 0x06b7, /* U+0407 CYRILLIC CAPITAL LETTER YI */ + XK_Ukranian_YI = 0x06b7, /* deprecated */ + XK_Cyrillic_JE = 0x06b8, /* U+0408 CYRILLIC CAPITAL LETTER JE */ + XK_Serbian_JE = 0x06b8, /* deprecated */ + XK_Cyrillic_LJE = 0x06b9, /* U+0409 CYRILLIC CAPITAL LETTER LJE */ + XK_Serbian_LJE = 0x06b9, /* deprecated */ + XK_Cyrillic_NJE = 0x06ba, /* U+040A CYRILLIC CAPITAL LETTER NJE */ + XK_Serbian_NJE = 0x06ba, /* deprecated */ + XK_Serbian_TSHE = 0x06bb, /* U+040B CYRILLIC CAPITAL LETTER TSHE */ + XK_Macedonia_KJE = 0x06bc, /* U+040C CYRILLIC CAPITAL LETTER KJE */ + XK_Ukrainian_GHE_WITH_UPTURN = 0x06bd, /* U+0490 CYRILLIC CAPITAL LETTER GHE WITH UPTURN */ + XK_Byelorussian_SHORTU = 0x06be, /* U+040E CYRILLIC CAPITAL LETTER SHORT U */ + XK_Cyrillic_DZHE = 0x06bf, /* U+040F CYRILLIC CAPITAL LETTER DZHE */ + XK_Serbian_DZE = 0x06bf, /* deprecated */ + XK_Cyrillic_yu = 0x06c0, /* U+044E CYRILLIC SMALL LETTER YU */ + XK_Cyrillic_a = 0x06c1, /* U+0430 CYRILLIC SMALL LETTER A */ + XK_Cyrillic_be = 0x06c2, /* U+0431 CYRILLIC SMALL LETTER BE */ + XK_Cyrillic_tse = 0x06c3, /* U+0446 CYRILLIC SMALL LETTER TSE */ + XK_Cyrillic_de = 0x06c4, /* U+0434 CYRILLIC SMALL LETTER DE */ + XK_Cyrillic_ie = 0x06c5, /* U+0435 CYRILLIC SMALL LETTER IE */ + XK_Cyrillic_ef = 0x06c6, /* U+0444 CYRILLIC SMALL LETTER EF */ + XK_Cyrillic_ghe = 0x06c7, /* U+0433 CYRILLIC SMALL LETTER GHE */ + XK_Cyrillic_ha = 0x06c8, /* U+0445 CYRILLIC SMALL LETTER HA */ + XK_Cyrillic_i = 0x06c9, /* U+0438 CYRILLIC SMALL LETTER I */ + XK_Cyrillic_shorti = 0x06ca, /* U+0439 CYRILLIC SMALL LETTER SHORT I */ + XK_Cyrillic_ka = 0x06cb, /* U+043A CYRILLIC SMALL LETTER KA */ + XK_Cyrillic_el = 0x06cc, /* U+043B CYRILLIC SMALL LETTER EL */ + XK_Cyrillic_em = 0x06cd, /* U+043C CYRILLIC SMALL LETTER EM */ + XK_Cyrillic_en = 0x06ce, /* U+043D CYRILLIC SMALL LETTER EN */ + XK_Cyrillic_o = 0x06cf, /* U+043E CYRILLIC SMALL LETTER O */ + XK_Cyrillic_pe = 0x06d0, /* U+043F CYRILLIC SMALL LETTER PE */ + XK_Cyrillic_ya = 0x06d1, /* U+044F CYRILLIC SMALL LETTER YA */ + XK_Cyrillic_er = 0x06d2, /* U+0440 CYRILLIC SMALL LETTER ER */ + XK_Cyrillic_es = 0x06d3, /* U+0441 CYRILLIC SMALL LETTER ES */ + XK_Cyrillic_te = 0x06d4, /* U+0442 CYRILLIC SMALL LETTER TE */ + XK_Cyrillic_u = 0x06d5, /* U+0443 CYRILLIC SMALL LETTER U */ + XK_Cyrillic_zhe = 0x06d6, /* U+0436 CYRILLIC SMALL LETTER ZHE */ + XK_Cyrillic_ve = 0x06d7, /* U+0432 CYRILLIC SMALL LETTER VE */ + XK_Cyrillic_softsign = 0x06d8, /* U+044C CYRILLIC SMALL LETTER SOFT SIGN */ + XK_Cyrillic_yeru = 0x06d9, /* U+044B CYRILLIC SMALL LETTER YERU */ + XK_Cyrillic_ze = 0x06da, /* U+0437 CYRILLIC SMALL LETTER ZE */ + XK_Cyrillic_sha = 0x06db, /* U+0448 CYRILLIC SMALL LETTER SHA */ + XK_Cyrillic_e = 0x06dc, /* U+044D CYRILLIC SMALL LETTER E */ + XK_Cyrillic_shcha = 0x06dd, /* U+0449 CYRILLIC SMALL LETTER SHCHA */ + XK_Cyrillic_che = 0x06de, /* U+0447 CYRILLIC SMALL LETTER CHE */ + XK_Cyrillic_hardsign = 0x06df, /* U+044A CYRILLIC SMALL LETTER HARD SIGN */ + XK_Cyrillic_YU = 0x06e0, /* U+042E CYRILLIC CAPITAL LETTER YU */ + XK_Cyrillic_A = 0x06e1, /* U+0410 CYRILLIC CAPITAL LETTER A */ + XK_Cyrillic_BE = 0x06e2, /* U+0411 CYRILLIC CAPITAL LETTER BE */ + XK_Cyrillic_TSE = 0x06e3, /* U+0426 CYRILLIC CAPITAL LETTER TSE */ + XK_Cyrillic_DE = 0x06e4, /* U+0414 CYRILLIC CAPITAL LETTER DE */ + XK_Cyrillic_IE = 0x06e5, /* U+0415 CYRILLIC CAPITAL LETTER IE */ + XK_Cyrillic_EF = 0x06e6, /* U+0424 CYRILLIC CAPITAL LETTER EF */ + XK_Cyrillic_GHE = 0x06e7, /* U+0413 CYRILLIC CAPITAL LETTER GHE */ + XK_Cyrillic_HA = 0x06e8, /* U+0425 CYRILLIC CAPITAL LETTER HA */ + XK_Cyrillic_I = 0x06e9, /* U+0418 CYRILLIC CAPITAL LETTER I */ + XK_Cyrillic_SHORTI = 0x06ea, /* U+0419 CYRILLIC CAPITAL LETTER SHORT I */ + XK_Cyrillic_KA = 0x06eb, /* U+041A CYRILLIC CAPITAL LETTER KA */ + XK_Cyrillic_EL = 0x06ec, /* U+041B CYRILLIC CAPITAL LETTER EL */ + XK_Cyrillic_EM = 0x06ed, /* U+041C CYRILLIC CAPITAL LETTER EM */ + XK_Cyrillic_EN = 0x06ee, /* U+041D CYRILLIC CAPITAL LETTER EN */ + XK_Cyrillic_O = 0x06ef, /* U+041E CYRILLIC CAPITAL LETTER O */ + XK_Cyrillic_PE = 0x06f0, /* U+041F CYRILLIC CAPITAL LETTER PE */ + XK_Cyrillic_YA = 0x06f1, /* U+042F CYRILLIC CAPITAL LETTER YA */ + XK_Cyrillic_ER = 0x06f2, /* U+0420 CYRILLIC CAPITAL LETTER ER */ + XK_Cyrillic_ES = 0x06f3, /* U+0421 CYRILLIC CAPITAL LETTER ES */ + XK_Cyrillic_TE = 0x06f4, /* U+0422 CYRILLIC CAPITAL LETTER TE */ + XK_Cyrillic_U = 0x06f5, /* U+0423 CYRILLIC CAPITAL LETTER U */ + XK_Cyrillic_ZHE = 0x06f6, /* U+0416 CYRILLIC CAPITAL LETTER ZHE */ + XK_Cyrillic_VE = 0x06f7, /* U+0412 CYRILLIC CAPITAL LETTER VE */ + XK_Cyrillic_SOFTSIGN = 0x06f8, /* U+042C CYRILLIC CAPITAL LETTER SOFT SIGN */ + XK_Cyrillic_YERU = 0x06f9, /* U+042B CYRILLIC CAPITAL LETTER YERU */ + XK_Cyrillic_ZE = 0x06fa, /* U+0417 CYRILLIC CAPITAL LETTER ZE */ + XK_Cyrillic_SHA = 0x06fb, /* U+0428 CYRILLIC CAPITAL LETTER SHA */ + XK_Cyrillic_E = 0x06fc, /* U+042D CYRILLIC CAPITAL LETTER E */ + XK_Cyrillic_SHCHA = 0x06fd, /* U+0429 CYRILLIC CAPITAL LETTER SHCHA */ + XK_Cyrillic_CHE = 0x06fe, /* U+0427 CYRILLIC CAPITAL LETTER CHE */ + XK_Cyrillic_HARDSIGN = 0x06ff, /* U+042A CYRILLIC CAPITAL LETTER HARD SIGN */ + XK_Greek_ALPHAaccent = 0x07a1, /* U+0386 GREEK CAPITAL LETTER ALPHA WITH TONOS */ + XK_Greek_EPSILONaccent = 0x07a2, /* U+0388 GREEK CAPITAL LETTER EPSILON WITH TONOS */ + XK_Greek_ETAaccent = 0x07a3, /* U+0389 GREEK CAPITAL LETTER ETA WITH TONOS */ + XK_Greek_IOTAaccent = 0x07a4, /* U+038A GREEK CAPITAL LETTER IOTA WITH TONOS */ + XK_Greek_IOTAdieresis = 0x07a5, /* U+03AA GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ + XK_Greek_IOTAdiaeresis = 0x07a5, /* old typo */ + XK_Greek_OMICRONaccent = 0x07a7, /* U+038C GREEK CAPITAL LETTER OMICRON WITH TONOS */ + XK_Greek_UPSILONaccent = 0x07a8, /* U+038E GREEK CAPITAL LETTER UPSILON WITH TONOS */ + XK_Greek_UPSILONdieresis = 0x07a9, /* U+03AB GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ + XK_Greek_OMEGAaccent = 0x07ab, /* U+038F GREEK CAPITAL LETTER OMEGA WITH TONOS */ + XK_Greek_accentdieresis = 0x07ae, /* U+0385 GREEK DIALYTIKA TONOS */ + XK_Greek_horizbar = 0x07af, /* U+2015 HORIZONTAL BAR */ + XK_Greek_alphaaccent = 0x07b1, /* U+03AC GREEK SMALL LETTER ALPHA WITH TONOS */ + XK_Greek_epsilonaccent = 0x07b2, /* U+03AD GREEK SMALL LETTER EPSILON WITH TONOS */ + XK_Greek_etaaccent = 0x07b3, /* U+03AE GREEK SMALL LETTER ETA WITH TONOS */ + XK_Greek_iotaaccent = 0x07b4, /* U+03AF GREEK SMALL LETTER IOTA WITH TONOS */ + XK_Greek_iotadieresis = 0x07b5, /* U+03CA GREEK SMALL LETTER IOTA WITH DIALYTIKA */ + XK_Greek_iotaaccentdieresis = 0x07b6, /* U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ + XK_Greek_omicronaccent = 0x07b7, /* U+03CC GREEK SMALL LETTER OMICRON WITH TONOS */ + XK_Greek_upsilonaccent = 0x07b8, /* U+03CD GREEK SMALL LETTER UPSILON WITH TONOS */ + XK_Greek_upsilondieresis = 0x07b9, /* U+03CB GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ + XK_Greek_upsilonaccentdieresis = 0x07ba, /* U+03B0 GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ + XK_Greek_omegaaccent = 0x07bb, /* U+03CE GREEK SMALL LETTER OMEGA WITH TONOS */ + XK_Greek_ALPHA = 0x07c1, /* U+0391 GREEK CAPITAL LETTER ALPHA */ + XK_Greek_BETA = 0x07c2, /* U+0392 GREEK CAPITAL LETTER BETA */ + XK_Greek_GAMMA = 0x07c3, /* U+0393 GREEK CAPITAL LETTER GAMMA */ + XK_Greek_DELTA = 0x07c4, /* U+0394 GREEK CAPITAL LETTER DELTA */ + XK_Greek_EPSILON = 0x07c5, /* U+0395 GREEK CAPITAL LETTER EPSILON */ + XK_Greek_ZETA = 0x07c6, /* U+0396 GREEK CAPITAL LETTER ZETA */ + XK_Greek_ETA = 0x07c7, /* U+0397 GREEK CAPITAL LETTER ETA */ + XK_Greek_THETA = 0x07c8, /* U+0398 GREEK CAPITAL LETTER THETA */ + XK_Greek_IOTA = 0x07c9, /* U+0399 GREEK CAPITAL LETTER IOTA */ + XK_Greek_KAPPA = 0x07ca, /* U+039A GREEK CAPITAL LETTER KAPPA */ + XK_Greek_LAMDA = 0x07cb, /* U+039B GREEK CAPITAL LETTER LAMDA */ + XK_Greek_LAMBDA = 0x07cb, /* U+039B GREEK CAPITAL LETTER LAMDA */ + XK_Greek_MU = 0x07cc, /* U+039C GREEK CAPITAL LETTER MU */ + XK_Greek_NU = 0x07cd, /* U+039D GREEK CAPITAL LETTER NU */ + XK_Greek_XI = 0x07ce, /* U+039E GREEK CAPITAL LETTER XI */ + XK_Greek_OMICRON = 0x07cf, /* U+039F GREEK CAPITAL LETTER OMICRON */ + XK_Greek_PI = 0x07d0, /* U+03A0 GREEK CAPITAL LETTER PI */ + XK_Greek_RHO = 0x07d1, /* U+03A1 GREEK CAPITAL LETTER RHO */ + XK_Greek_SIGMA = 0x07d2, /* U+03A3 GREEK CAPITAL LETTER SIGMA */ + XK_Greek_TAU = 0x07d4, /* U+03A4 GREEK CAPITAL LETTER TAU */ + XK_Greek_UPSILON = 0x07d5, /* U+03A5 GREEK CAPITAL LETTER UPSILON */ + XK_Greek_PHI = 0x07d6, /* U+03A6 GREEK CAPITAL LETTER PHI */ + XK_Greek_CHI = 0x07d7, /* U+03A7 GREEK CAPITAL LETTER CHI */ + XK_Greek_PSI = 0x07d8, /* U+03A8 GREEK CAPITAL LETTER PSI */ + XK_Greek_OMEGA = 0x07d9, /* U+03A9 GREEK CAPITAL LETTER OMEGA */ + XK_Greek_alpha = 0x07e1, /* U+03B1 GREEK SMALL LETTER ALPHA */ + XK_Greek_beta = 0x07e2, /* U+03B2 GREEK SMALL LETTER BETA */ + XK_Greek_gamma = 0x07e3, /* U+03B3 GREEK SMALL LETTER GAMMA */ + XK_Greek_delta = 0x07e4, /* U+03B4 GREEK SMALL LETTER DELTA */ + XK_Greek_epsilon = 0x07e5, /* U+03B5 GREEK SMALL LETTER EPSILON */ + XK_Greek_zeta = 0x07e6, /* U+03B6 GREEK SMALL LETTER ZETA */ + XK_Greek_eta = 0x07e7, /* U+03B7 GREEK SMALL LETTER ETA */ + XK_Greek_theta = 0x07e8, /* U+03B8 GREEK SMALL LETTER THETA */ + XK_Greek_iota = 0x07e9, /* U+03B9 GREEK SMALL LETTER IOTA */ + XK_Greek_kappa = 0x07ea, /* U+03BA GREEK SMALL LETTER KAPPA */ + XK_Greek_lamda = 0x07eb, /* U+03BB GREEK SMALL LETTER LAMDA */ + XK_Greek_lambda = 0x07eb, /* U+03BB GREEK SMALL LETTER LAMDA */ + XK_Greek_mu = 0x07ec, /* U+03BC GREEK SMALL LETTER MU */ + XK_Greek_nu = 0x07ed, /* U+03BD GREEK SMALL LETTER NU */ + XK_Greek_xi = 0x07ee, /* U+03BE GREEK SMALL LETTER XI */ + XK_Greek_omicron = 0x07ef, /* U+03BF GREEK SMALL LETTER OMICRON */ + XK_Greek_pi = 0x07f0, /* U+03C0 GREEK SMALL LETTER PI */ + XK_Greek_rho = 0x07f1, /* U+03C1 GREEK SMALL LETTER RHO */ + XK_Greek_sigma = 0x07f2, /* U+03C3 GREEK SMALL LETTER SIGMA */ + XK_Greek_finalsmallsigma = 0x07f3, /* U+03C2 GREEK SMALL LETTER FINAL SIGMA */ + XK_Greek_tau = 0x07f4, /* U+03C4 GREEK SMALL LETTER TAU */ + XK_Greek_upsilon = 0x07f5, /* U+03C5 GREEK SMALL LETTER UPSILON */ + XK_Greek_phi = 0x07f6, /* U+03C6 GREEK SMALL LETTER PHI */ + XK_Greek_chi = 0x07f7, /* U+03C7 GREEK SMALL LETTER CHI */ + XK_Greek_psi = 0x07f8, /* U+03C8 GREEK SMALL LETTER PSI */ + XK_Greek_omega = 0x07f9, /* U+03C9 GREEK SMALL LETTER OMEGA */ + XK_Greek_switch = 0xff7e, /* Alias for mode_switch */ + XK_leftradical = 0x08a1, /* U+23B7 RADICAL SYMBOL BOTTOM */ + XK_topleftradical = 0x08a2, /*(U+250C BOX DRAWINGS LIGHT DOWN AND RIGHT)*/ + XK_horizconnector = 0x08a3, /*(U+2500 BOX DRAWINGS LIGHT HORIZONTAL)*/ + XK_topintegral = 0x08a4, /* U+2320 TOP HALF INTEGRAL */ + XK_botintegral = 0x08a5, /* U+2321 BOTTOM HALF INTEGRAL */ + XK_vertconnector = 0x08a6, /*(U+2502 BOX DRAWINGS LIGHT VERTICAL)*/ + XK_topleftsqbracket = 0x08a7, /* U+23A1 LEFT SQUARE BRACKET UPPER CORNER */ + XK_botleftsqbracket = 0x08a8, /* U+23A3 LEFT SQUARE BRACKET LOWER CORNER */ + XK_toprightsqbracket = 0x08a9, /* U+23A4 RIGHT SQUARE BRACKET UPPER CORNER */ + XK_botrightsqbracket = 0x08aa, /* U+23A6 RIGHT SQUARE BRACKET LOWER CORNER */ + XK_topleftparens = 0x08ab, /* U+239B LEFT PARENTHESIS UPPER HOOK */ + XK_botleftparens = 0x08ac, /* U+239D LEFT PARENTHESIS LOWER HOOK */ + XK_toprightparens = 0x08ad, /* U+239E RIGHT PARENTHESIS UPPER HOOK */ + XK_botrightparens = 0x08ae, /* U+23A0 RIGHT PARENTHESIS LOWER HOOK */ + XK_leftmiddlecurlybrace = 0x08af, /* U+23A8 LEFT CURLY BRACKET MIDDLE PIECE */ + XK_rightmiddlecurlybrace = 0x08b0, /* U+23AC RIGHT CURLY BRACKET MIDDLE PIECE */ + XK_topleftsummation = 0x08b1, + XK_botleftsummation = 0x08b2, + XK_topvertsummationconnector = 0x08b3, + XK_botvertsummationconnector = 0x08b4, + XK_toprightsummation = 0x08b5, + XK_botrightsummation = 0x08b6, + XK_rightmiddlesummation = 0x08b7, + XK_lessthanequal = 0x08bc, /* U+2264 LESS-THAN OR EQUAL TO */ + XK_notequal = 0x08bd, /* U+2260 NOT EQUAL TO */ + XK_greaterthanequal = 0x08be, /* U+2265 GREATER-THAN OR EQUAL TO */ + XK_integral = 0x08bf, /* U+222B INTEGRAL */ + XK_therefore = 0x08c0, /* U+2234 THEREFORE */ + XK_variation = 0x08c1, /* U+221D PROPORTIONAL TO */ + XK_infinity = 0x08c2, /* U+221E INFINITY */ + XK_nabla = 0x08c5, /* U+2207 NABLA */ + XK_approximate = 0x08c8, /* U+223C TILDE OPERATOR */ + XK_similarequal = 0x08c9, /* U+2243 ASYMPTOTICALLY EQUAL TO */ + XK_ifonlyif = 0x08cd, /* U+21D4 LEFT RIGHT DOUBLE ARROW */ + XK_implies = 0x08ce, /* U+21D2 RIGHTWARDS DOUBLE ARROW */ + XK_identical = 0x08cf, /* U+2261 IDENTICAL TO */ + XK_radical = 0x08d6, /* U+221A SQUARE ROOT */ + XK_includedin = 0x08da, /* U+2282 SUBSET OF */ + XK_includes = 0x08db, /* U+2283 SUPERSET OF */ + XK_intersection = 0x08dc, /* U+2229 INTERSECTION */ + XK_union = 0x08dd, /* U+222A UNION */ + XK_logicaland = 0x08de, /* U+2227 LOGICAL AND */ + XK_logicalor = 0x08df, /* U+2228 LOGICAL OR */ + XK_partialderivative = 0x08ef, /* U+2202 PARTIAL DIFFERENTIAL */ + XK_function = 0x08f6, /* U+0192 LATIN SMALL LETTER F WITH HOOK */ + XK_leftarrow = 0x08fb, /* U+2190 LEFTWARDS ARROW */ + XK_uparrow = 0x08fc, /* U+2191 UPWARDS ARROW */ + XK_rightarrow = 0x08fd, /* U+2192 RIGHTWARDS ARROW */ + XK_downarrow = 0x08fe, /* U+2193 DOWNWARDS ARROW */ + XK_blank = 0x09df, + XK_soliddiamond = 0x09e0, /* U+25C6 BLACK DIAMOND */ + XK_checkerboard = 0x09e1, /* U+2592 MEDIUM SHADE */ + XK_ht = 0x09e2, /* U+2409 SYMBOL FOR HORIZONTAL TABULATION */ + XK_ff = 0x09e3, /* U+240C SYMBOL FOR FORM FEED */ + XK_cr = 0x09e4, /* U+240D SYMBOL FOR CARRIAGE RETURN */ + XK_lf = 0x09e5, /* U+240A SYMBOL FOR LINE FEED */ + XK_nl = 0x09e8, /* U+2424 SYMBOL FOR NEWLINE */ + XK_vt = 0x09e9, /* U+240B SYMBOL FOR VERTICAL TABULATION */ + XK_lowrightcorner = 0x09ea, /* U+2518 BOX DRAWINGS LIGHT UP AND LEFT */ + XK_uprightcorner = 0x09eb, /* U+2510 BOX DRAWINGS LIGHT DOWN AND LEFT */ + XK_upleftcorner = 0x09ec, /* U+250C BOX DRAWINGS LIGHT DOWN AND RIGHT */ + XK_lowleftcorner = 0x09ed, /* U+2514 BOX DRAWINGS LIGHT UP AND RIGHT */ + XK_crossinglines = 0x09ee, /* U+253C BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ + XK_horizlinescan1 = 0x09ef, /* U+23BA HORIZONTAL SCAN LINE-1 */ + XK_horizlinescan3 = 0x09f0, /* U+23BB HORIZONTAL SCAN LINE-3 */ + XK_horizlinescan5 = 0x09f1, /* U+2500 BOX DRAWINGS LIGHT HORIZONTAL */ + XK_horizlinescan7 = 0x09f2, /* U+23BC HORIZONTAL SCAN LINE-7 */ + XK_horizlinescan9 = 0x09f3, /* U+23BD HORIZONTAL SCAN LINE-9 */ + XK_leftt = 0x09f4, /* U+251C BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ + XK_rightt = 0x09f5, /* U+2524 BOX DRAWINGS LIGHT VERTICAL AND LEFT */ + XK_bott = 0x09f6, /* U+2534 BOX DRAWINGS LIGHT UP AND HORIZONTAL */ + XK_topt = 0x09f7, /* U+252C BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ + XK_vertbar = 0x09f8, /* U+2502 BOX DRAWINGS LIGHT VERTICAL */ + XK_emspace = 0x0aa1, /* U+2003 EM SPACE */ + XK_enspace = 0x0aa2, /* U+2002 EN SPACE */ + XK_em3space = 0x0aa3, /* U+2004 THREE-PER-EM SPACE */ + XK_em4space = 0x0aa4, /* U+2005 FOUR-PER-EM SPACE */ + XK_digitspace = 0x0aa5, /* U+2007 FIGURE SPACE */ + XK_punctspace = 0x0aa6, /* U+2008 PUNCTUATION SPACE */ + XK_thinspace = 0x0aa7, /* U+2009 THIN SPACE */ + XK_hairspace = 0x0aa8, /* U+200A HAIR SPACE */ + XK_emdash = 0x0aa9, /* U+2014 EM DASH */ + XK_endash = 0x0aaa, /* U+2013 EN DASH */ + XK_signifblank = 0x0aac, /*(U+2423 OPEN BOX)*/ + XK_ellipsis = 0x0aae, /* U+2026 HORIZONTAL ELLIPSIS */ + XK_doubbaselinedot = 0x0aaf, /* U+2025 TWO DOT LEADER */ + XK_onethird = 0x0ab0, /* U+2153 VULGAR FRACTION ONE THIRD */ + XK_twothirds = 0x0ab1, /* U+2154 VULGAR FRACTION TWO THIRDS */ + XK_onefifth = 0x0ab2, /* U+2155 VULGAR FRACTION ONE FIFTH */ + XK_twofifths = 0x0ab3, /* U+2156 VULGAR FRACTION TWO FIFTHS */ + XK_threefifths = 0x0ab4, /* U+2157 VULGAR FRACTION THREE FIFTHS */ + XK_fourfifths = 0x0ab5, /* U+2158 VULGAR FRACTION FOUR FIFTHS */ + XK_onesixth = 0x0ab6, /* U+2159 VULGAR FRACTION ONE SIXTH */ + XK_fivesixths = 0x0ab7, /* U+215A VULGAR FRACTION FIVE SIXTHS */ + XK_careof = 0x0ab8, /* U+2105 CARE OF */ + XK_figdash = 0x0abb, /* U+2012 FIGURE DASH */ + XK_leftanglebracket = 0x0abc, /*(U+27E8 MATHEMATICAL LEFT ANGLE BRACKET)*/ + XK_decimalpoint = 0x0abd, /*(U+002E FULL STOP)*/ + XK_rightanglebracket = 0x0abe, /*(U+27E9 MATHEMATICAL RIGHT ANGLE BRACKET)*/ + XK_marker = 0x0abf, + XK_oneeighth = 0x0ac3, /* U+215B VULGAR FRACTION ONE EIGHTH */ + XK_threeeighths = 0x0ac4, /* U+215C VULGAR FRACTION THREE EIGHTHS */ + XK_fiveeighths = 0x0ac5, /* U+215D VULGAR FRACTION FIVE EIGHTHS */ + XK_seveneighths = 0x0ac6, /* U+215E VULGAR FRACTION SEVEN EIGHTHS */ + XK_trademark = 0x0ac9, /* U+2122 TRADE MARK SIGN */ + XK_signaturemark = 0x0aca, /*(U+2613 SALTIRE)*/ + XK_trademarkincircle = 0x0acb, + XK_leftopentriangle = 0x0acc, /*(U+25C1 WHITE LEFT-POINTING TRIANGLE)*/ + XK_rightopentriangle = 0x0acd, /*(U+25B7 WHITE RIGHT-POINTING TRIANGLE)*/ + XK_emopencircle = 0x0ace, /*(U+25CB WHITE CIRCLE)*/ + XK_emopenrectangle = 0x0acf, /*(U+25AF WHITE VERTICAL RECTANGLE)*/ + XK_leftsinglequotemark = 0x0ad0, /* U+2018 LEFT SINGLE QUOTATION MARK */ + XK_rightsinglequotemark = 0x0ad1, /* U+2019 RIGHT SINGLE QUOTATION MARK */ + XK_leftdoublequotemark = 0x0ad2, /* U+201C LEFT DOUBLE QUOTATION MARK */ + XK_rightdoublequotemark = 0x0ad3, /* U+201D RIGHT DOUBLE QUOTATION MARK */ + XK_prescription = 0x0ad4, /* U+211E PRESCRIPTION TAKE */ + XK_minutes = 0x0ad6, /* U+2032 PRIME */ + XK_seconds = 0x0ad7, /* U+2033 DOUBLE PRIME */ + XK_latincross = 0x0ad9, /* U+271D LATIN CROSS */ + XK_hexagram = 0x0ada, + XK_filledrectbullet = 0x0adb, /*(U+25AC BLACK RECTANGLE)*/ + XK_filledlefttribullet = 0x0adc, /*(U+25C0 BLACK LEFT-POINTING TRIANGLE)*/ + XK_filledrighttribullet = 0x0add, /*(U+25B6 BLACK RIGHT-POINTING TRIANGLE)*/ + XK_emfilledcircle = 0x0ade, /*(U+25CF BLACK CIRCLE)*/ + XK_emfilledrect = 0x0adf, /*(U+25AE BLACK VERTICAL RECTANGLE)*/ + XK_enopencircbullet = 0x0ae0, /*(U+25E6 WHITE BULLET)*/ + XK_enopensquarebullet = 0x0ae1, /*(U+25AB WHITE SMALL SQUARE)*/ + XK_openrectbullet = 0x0ae2, /*(U+25AD WHITE RECTANGLE)*/ + XK_opentribulletup = 0x0ae3, /*(U+25B3 WHITE UP-POINTING TRIANGLE)*/ + XK_opentribulletdown = 0x0ae4, /*(U+25BD WHITE DOWN-POINTING TRIANGLE)*/ + XK_openstar = 0x0ae5, /*(U+2606 WHITE STAR)*/ + XK_enfilledcircbullet = 0x0ae6, /*(U+2022 BULLET)*/ + XK_enfilledsqbullet = 0x0ae7, /*(U+25AA BLACK SMALL SQUARE)*/ + XK_filledtribulletup = 0x0ae8, /*(U+25B2 BLACK UP-POINTING TRIANGLE)*/ + XK_filledtribulletdown = 0x0ae9, /*(U+25BC BLACK DOWN-POINTING TRIANGLE)*/ + XK_leftpointer = 0x0aea, /*(U+261C WHITE LEFT POINTING INDEX)*/ + XK_rightpointer = 0x0aeb, /*(U+261E WHITE RIGHT POINTING INDEX)*/ + XK_club = 0x0aec, /* U+2663 BLACK CLUB SUIT */ + XK_diamond = 0x0aed, /* U+2666 BLACK DIAMOND SUIT */ + XK_heart = 0x0aee, /* U+2665 BLACK HEART SUIT */ + XK_maltesecross = 0x0af0, /* U+2720 MALTESE CROSS */ + XK_dagger = 0x0af1, /* U+2020 DAGGER */ + XK_doubledagger = 0x0af2, /* U+2021 DOUBLE DAGGER */ + XK_checkmark = 0x0af3, /* U+2713 CHECK MARK */ + XK_ballotcross = 0x0af4, /* U+2717 BALLOT X */ + XK_musicalsharp = 0x0af5, /* U+266F MUSIC SHARP SIGN */ + XK_musicalflat = 0x0af6, /* U+266D MUSIC FLAT SIGN */ + XK_malesymbol = 0x0af7, /* U+2642 MALE SIGN */ + XK_femalesymbol = 0x0af8, /* U+2640 FEMALE SIGN */ + XK_telephone = 0x0af9, /* U+260E BLACK TELEPHONE */ + XK_telephonerecorder = 0x0afa, /* U+2315 TELEPHONE RECORDER */ + XK_phonographcopyright = 0x0afb, /* U+2117 SOUND RECORDING COPYRIGHT */ + XK_caret = 0x0afc, /* U+2038 CARET */ + XK_singlelowquotemark = 0x0afd, /* U+201A SINGLE LOW-9 QUOTATION MARK */ + XK_doublelowquotemark = 0x0afe, /* U+201E DOUBLE LOW-9 QUOTATION MARK */ + XK_cursor = 0x0aff, + XK_leftcaret = 0x0ba3, /*(U+003C LESS-THAN SIGN)*/ + XK_rightcaret = 0x0ba6, /*(U+003E GREATER-THAN SIGN)*/ + XK_downcaret = 0x0ba8, /*(U+2228 LOGICAL OR)*/ + XK_upcaret = 0x0ba9, /*(U+2227 LOGICAL AND)*/ + XK_overbar = 0x0bc0, /*(U+00AF MACRON)*/ + XK_downtack = 0x0bc2, /* U+22A5 UP TACK */ + XK_upshoe = 0x0bc3, /*(U+2229 INTERSECTION)*/ + XK_downstile = 0x0bc4, /* U+230A LEFT FLOOR */ + XK_underbar = 0x0bc6, /*(U+005F LOW LINE)*/ + XK_jot = 0x0bca, /* U+2218 RING OPERATOR */ + XK_quad = 0x0bcc, /* U+2395 APL FUNCTIONAL SYMBOL QUAD */ + XK_uptack = 0x0bce, /* U+22A4 DOWN TACK */ + XK_circle = 0x0bcf, /* U+25CB WHITE CIRCLE */ + XK_upstile = 0x0bd3, /* U+2308 LEFT CEILING */ + XK_downshoe = 0x0bd6, /*(U+222A UNION)*/ + XK_rightshoe = 0x0bd8, /*(U+2283 SUPERSET OF)*/ + XK_leftshoe = 0x0bda, /*(U+2282 SUBSET OF)*/ + XK_lefttack = 0x0bdc, /* U+22A2 RIGHT TACK */ + XK_righttack = 0x0bfc, /* U+22A3 LEFT TACK */ + XK_hebrew_doublelowline = 0x0cdf, /* U+2017 DOUBLE LOW LINE */ + XK_hebrew_aleph = 0x0ce0, /* U+05D0 HEBREW LETTER ALEF */ + XK_hebrew_bet = 0x0ce1, /* U+05D1 HEBREW LETTER BET */ + XK_hebrew_beth = 0x0ce1, /* deprecated */ + XK_hebrew_gimel = 0x0ce2, /* U+05D2 HEBREW LETTER GIMEL */ + XK_hebrew_gimmel = 0x0ce2, /* deprecated */ + XK_hebrew_dalet = 0x0ce3, /* U+05D3 HEBREW LETTER DALET */ + XK_hebrew_daleth = 0x0ce3, /* deprecated */ + XK_hebrew_he = 0x0ce4, /* U+05D4 HEBREW LETTER HE */ + XK_hebrew_waw = 0x0ce5, /* U+05D5 HEBREW LETTER VAV */ + XK_hebrew_zain = 0x0ce6, /* U+05D6 HEBREW LETTER ZAYIN */ + XK_hebrew_zayin = 0x0ce6, /* deprecated */ + XK_hebrew_chet = 0x0ce7, /* U+05D7 HEBREW LETTER HET */ + XK_hebrew_het = 0x0ce7, /* deprecated */ + XK_hebrew_tet = 0x0ce8, /* U+05D8 HEBREW LETTER TET */ + XK_hebrew_teth = 0x0ce8, /* deprecated */ + XK_hebrew_yod = 0x0ce9, /* U+05D9 HEBREW LETTER YOD */ + XK_hebrew_finalkaph = 0x0cea, /* U+05DA HEBREW LETTER FINAL KAF */ + XK_hebrew_kaph = 0x0ceb, /* U+05DB HEBREW LETTER KAF */ + XK_hebrew_lamed = 0x0cec, /* U+05DC HEBREW LETTER LAMED */ + XK_hebrew_finalmem = 0x0ced, /* U+05DD HEBREW LETTER FINAL MEM */ + XK_hebrew_mem = 0x0cee, /* U+05DE HEBREW LETTER MEM */ + XK_hebrew_finalnun = 0x0cef, /* U+05DF HEBREW LETTER FINAL NUN */ + XK_hebrew_nun = 0x0cf0, /* U+05E0 HEBREW LETTER NUN */ + XK_hebrew_samech = 0x0cf1, /* U+05E1 HEBREW LETTER SAMEKH */ + XK_hebrew_samekh = 0x0cf1, /* deprecated */ + XK_hebrew_ayin = 0x0cf2, /* U+05E2 HEBREW LETTER AYIN */ + XK_hebrew_finalpe = 0x0cf3, /* U+05E3 HEBREW LETTER FINAL PE */ + XK_hebrew_pe = 0x0cf4, /* U+05E4 HEBREW LETTER PE */ + XK_hebrew_finalzade = 0x0cf5, /* U+05E5 HEBREW LETTER FINAL TSADI */ + XK_hebrew_finalzadi = 0x0cf5, /* deprecated */ + XK_hebrew_zade = 0x0cf6, /* U+05E6 HEBREW LETTER TSADI */ + XK_hebrew_zadi = 0x0cf6, /* deprecated */ + XK_hebrew_qoph = 0x0cf7, /* U+05E7 HEBREW LETTER QOF */ + XK_hebrew_kuf = 0x0cf7, /* deprecated */ + XK_hebrew_resh = 0x0cf8, /* U+05E8 HEBREW LETTER RESH */ + XK_hebrew_shin = 0x0cf9, /* U+05E9 HEBREW LETTER SHIN */ + XK_hebrew_taw = 0x0cfa, /* U+05EA HEBREW LETTER TAV */ + XK_hebrew_taf = 0x0cfa, /* deprecated */ + XK_Hebrew_switch = 0xff7e, /* Alias for mode_switch */ + XK_Thai_kokai = 0x0da1, /* U+0E01 THAI CHARACTER KO KAI */ + XK_Thai_khokhai = 0x0da2, /* U+0E02 THAI CHARACTER KHO KHAI */ + XK_Thai_khokhuat = 0x0da3, /* U+0E03 THAI CHARACTER KHO KHUAT */ + XK_Thai_khokhwai = 0x0da4, /* U+0E04 THAI CHARACTER KHO KHWAI */ + XK_Thai_khokhon = 0x0da5, /* U+0E05 THAI CHARACTER KHO KHON */ + XK_Thai_khorakhang = 0x0da6, /* U+0E06 THAI CHARACTER KHO RAKHANG */ + XK_Thai_ngongu = 0x0da7, /* U+0E07 THAI CHARACTER NGO NGU */ + XK_Thai_chochan = 0x0da8, /* U+0E08 THAI CHARACTER CHO CHAN */ + XK_Thai_choching = 0x0da9, /* U+0E09 THAI CHARACTER CHO CHING */ + XK_Thai_chochang = 0x0daa, /* U+0E0A THAI CHARACTER CHO CHANG */ + XK_Thai_soso = 0x0dab, /* U+0E0B THAI CHARACTER SO SO */ + XK_Thai_chochoe = 0x0dac, /* U+0E0C THAI CHARACTER CHO CHOE */ + XK_Thai_yoying = 0x0dad, /* U+0E0D THAI CHARACTER YO YING */ + XK_Thai_dochada = 0x0dae, /* U+0E0E THAI CHARACTER DO CHADA */ + XK_Thai_topatak = 0x0daf, /* U+0E0F THAI CHARACTER TO PATAK */ + XK_Thai_thothan = 0x0db0, /* U+0E10 THAI CHARACTER THO THAN */ + XK_Thai_thonangmontho = 0x0db1, /* U+0E11 THAI CHARACTER THO NANGMONTHO */ + XK_Thai_thophuthao = 0x0db2, /* U+0E12 THAI CHARACTER THO PHUTHAO */ + XK_Thai_nonen = 0x0db3, /* U+0E13 THAI CHARACTER NO NEN */ + XK_Thai_dodek = 0x0db4, /* U+0E14 THAI CHARACTER DO DEK */ + XK_Thai_totao = 0x0db5, /* U+0E15 THAI CHARACTER TO TAO */ + XK_Thai_thothung = 0x0db6, /* U+0E16 THAI CHARACTER THO THUNG */ + XK_Thai_thothahan = 0x0db7, /* U+0E17 THAI CHARACTER THO THAHAN */ + XK_Thai_thothong = 0x0db8, /* U+0E18 THAI CHARACTER THO THONG */ + XK_Thai_nonu = 0x0db9, /* U+0E19 THAI CHARACTER NO NU */ + XK_Thai_bobaimai = 0x0dba, /* U+0E1A THAI CHARACTER BO BAIMAI */ + XK_Thai_popla = 0x0dbb, /* U+0E1B THAI CHARACTER PO PLA */ + XK_Thai_phophung = 0x0dbc, /* U+0E1C THAI CHARACTER PHO PHUNG */ + XK_Thai_fofa = 0x0dbd, /* U+0E1D THAI CHARACTER FO FA */ + XK_Thai_phophan = 0x0dbe, /* U+0E1E THAI CHARACTER PHO PHAN */ + XK_Thai_fofan = 0x0dbf, /* U+0E1F THAI CHARACTER FO FAN */ + XK_Thai_phosamphao = 0x0dc0, /* U+0E20 THAI CHARACTER PHO SAMPHAO */ + XK_Thai_moma = 0x0dc1, /* U+0E21 THAI CHARACTER MO MA */ + XK_Thai_yoyak = 0x0dc2, /* U+0E22 THAI CHARACTER YO YAK */ + XK_Thai_rorua = 0x0dc3, /* U+0E23 THAI CHARACTER RO RUA */ + XK_Thai_ru = 0x0dc4, /* U+0E24 THAI CHARACTER RU */ + XK_Thai_loling = 0x0dc5, /* U+0E25 THAI CHARACTER LO LING */ + XK_Thai_lu = 0x0dc6, /* U+0E26 THAI CHARACTER LU */ + XK_Thai_wowaen = 0x0dc7, /* U+0E27 THAI CHARACTER WO WAEN */ + XK_Thai_sosala = 0x0dc8, /* U+0E28 THAI CHARACTER SO SALA */ + XK_Thai_sorusi = 0x0dc9, /* U+0E29 THAI CHARACTER SO RUSI */ + XK_Thai_sosua = 0x0dca, /* U+0E2A THAI CHARACTER SO SUA */ + XK_Thai_hohip = 0x0dcb, /* U+0E2B THAI CHARACTER HO HIP */ + XK_Thai_lochula = 0x0dcc, /* U+0E2C THAI CHARACTER LO CHULA */ + XK_Thai_oang = 0x0dcd, /* U+0E2D THAI CHARACTER O ANG */ + XK_Thai_honokhuk = 0x0dce, /* U+0E2E THAI CHARACTER HO NOKHUK */ + XK_Thai_paiyannoi = 0x0dcf, /* U+0E2F THAI CHARACTER PAIYANNOI */ + XK_Thai_saraa = 0x0dd0, /* U+0E30 THAI CHARACTER SARA A */ + XK_Thai_maihanakat = 0x0dd1, /* U+0E31 THAI CHARACTER MAI HAN-AKAT */ + XK_Thai_saraaa = 0x0dd2, /* U+0E32 THAI CHARACTER SARA AA */ + XK_Thai_saraam = 0x0dd3, /* U+0E33 THAI CHARACTER SARA AM */ + XK_Thai_sarai = 0x0dd4, /* U+0E34 THAI CHARACTER SARA I */ + XK_Thai_saraii = 0x0dd5, /* U+0E35 THAI CHARACTER SARA II */ + XK_Thai_saraue = 0x0dd6, /* U+0E36 THAI CHARACTER SARA UE */ + XK_Thai_sarauee = 0x0dd7, /* U+0E37 THAI CHARACTER SARA UEE */ + XK_Thai_sarau = 0x0dd8, /* U+0E38 THAI CHARACTER SARA U */ + XK_Thai_sarauu = 0x0dd9, /* U+0E39 THAI CHARACTER SARA UU */ + XK_Thai_phinthu = 0x0dda, /* U+0E3A THAI CHARACTER PHINTHU */ + XK_Thai_maihanakat_maitho = 0x0dde, + XK_Thai_baht = 0x0ddf, /* U+0E3F THAI CURRENCY SYMBOL BAHT */ + XK_Thai_sarae = 0x0de0, /* U+0E40 THAI CHARACTER SARA E */ + XK_Thai_saraae = 0x0de1, /* U+0E41 THAI CHARACTER SARA AE */ + XK_Thai_sarao = 0x0de2, /* U+0E42 THAI CHARACTER SARA O */ + XK_Thai_saraaimaimuan = 0x0de3, /* U+0E43 THAI CHARACTER SARA AI MAIMUAN */ + XK_Thai_saraaimaimalai = 0x0de4, /* U+0E44 THAI CHARACTER SARA AI MAIMALAI */ + XK_Thai_lakkhangyao = 0x0de5, /* U+0E45 THAI CHARACTER LAKKHANGYAO */ + XK_Thai_maiyamok = 0x0de6, /* U+0E46 THAI CHARACTER MAIYAMOK */ + XK_Thai_maitaikhu = 0x0de7, /* U+0E47 THAI CHARACTER MAITAIKHU */ + XK_Thai_maiek = 0x0de8, /* U+0E48 THAI CHARACTER MAI EK */ + XK_Thai_maitho = 0x0de9, /* U+0E49 THAI CHARACTER MAI THO */ + XK_Thai_maitri = 0x0dea, /* U+0E4A THAI CHARACTER MAI TRI */ + XK_Thai_maichattawa = 0x0deb, /* U+0E4B THAI CHARACTER MAI CHATTAWA */ + XK_Thai_thanthakhat = 0x0dec, /* U+0E4C THAI CHARACTER THANTHAKHAT */ + XK_Thai_nikhahit = 0x0ded, /* U+0E4D THAI CHARACTER NIKHAHIT */ + XK_Thai_leksun = 0x0df0, /* U+0E50 THAI DIGIT ZERO */ + XK_Thai_leknung = 0x0df1, /* U+0E51 THAI DIGIT ONE */ + XK_Thai_leksong = 0x0df2, /* U+0E52 THAI DIGIT TWO */ + XK_Thai_leksam = 0x0df3, /* U+0E53 THAI DIGIT THREE */ + XK_Thai_leksi = 0x0df4, /* U+0E54 THAI DIGIT FOUR */ + XK_Thai_lekha = 0x0df5, /* U+0E55 THAI DIGIT FIVE */ + XK_Thai_lekhok = 0x0df6, /* U+0E56 THAI DIGIT SIX */ + XK_Thai_lekchet = 0x0df7, /* U+0E57 THAI DIGIT SEVEN */ + XK_Thai_lekpaet = 0x0df8, /* U+0E58 THAI DIGIT EIGHT */ + XK_Thai_lekkao = 0x0df9, /* U+0E59 THAI DIGIT NINE */ + XK_Hangul = 0xff31, /* Hangul start/stop(toggle) */ + XK_Hangul_Start = 0xff32, /* Hangul start */ + XK_Hangul_End = 0xff33, /* Hangul end, English start */ + XK_Hangul_Hanja = 0xff34, /* Start Hangul->Hanja Conversion */ + XK_Hangul_Jamo = 0xff35, /* Hangul Jamo mode */ + XK_Hangul_Romaja = 0xff36, /* Hangul Romaja mode */ + XK_Hangul_Codeinput = 0xff37, /* Hangul code input mode */ + XK_Hangul_Jeonja = 0xff38, /* Jeonja mode */ + XK_Hangul_Banja = 0xff39, /* Banja mode */ + XK_Hangul_PreHanja = 0xff3a, /* Pre Hanja conversion */ + XK_Hangul_PostHanja = 0xff3b, /* Post Hanja conversion */ + XK_Hangul_SingleCandidate = 0xff3c, /* Single candidate */ + XK_Hangul_MultipleCandidate = 0xff3d, /* Multiple candidate */ + XK_Hangul_PreviousCandidate = 0xff3e, /* Previous candidate */ + XK_Hangul_Special = 0xff3f, /* Special symbols */ + XK_Hangul_switch = 0xff7e, /* Alias for mode_switch */ + XK_Hangul_Kiyeog = 0x0ea1, + XK_Hangul_SsangKiyeog = 0x0ea2, + XK_Hangul_KiyeogSios = 0x0ea3, + XK_Hangul_Nieun = 0x0ea4, + XK_Hangul_NieunJieuj = 0x0ea5, + XK_Hangul_NieunHieuh = 0x0ea6, + XK_Hangul_Dikeud = 0x0ea7, + XK_Hangul_SsangDikeud = 0x0ea8, + XK_Hangul_Rieul = 0x0ea9, + XK_Hangul_RieulKiyeog = 0x0eaa, + XK_Hangul_RieulMieum = 0x0eab, + XK_Hangul_RieulPieub = 0x0eac, + XK_Hangul_RieulSios = 0x0ead, + XK_Hangul_RieulTieut = 0x0eae, + XK_Hangul_RieulPhieuf = 0x0eaf, + XK_Hangul_RieulHieuh = 0x0eb0, + XK_Hangul_Mieum = 0x0eb1, + XK_Hangul_Pieub = 0x0eb2, + XK_Hangul_SsangPieub = 0x0eb3, + XK_Hangul_PieubSios = 0x0eb4, + XK_Hangul_Sios = 0x0eb5, + XK_Hangul_SsangSios = 0x0eb6, + XK_Hangul_Ieung = 0x0eb7, + XK_Hangul_Jieuj = 0x0eb8, + XK_Hangul_SsangJieuj = 0x0eb9, + XK_Hangul_Cieuc = 0x0eba, + XK_Hangul_Khieuq = 0x0ebb, + XK_Hangul_Tieut = 0x0ebc, + XK_Hangul_Phieuf = 0x0ebd, + XK_Hangul_Hieuh = 0x0ebe, + XK_Hangul_A = 0x0ebf, + XK_Hangul_AE = 0x0ec0, + XK_Hangul_YA = 0x0ec1, + XK_Hangul_YAE = 0x0ec2, + XK_Hangul_EO = 0x0ec3, + XK_Hangul_E = 0x0ec4, + XK_Hangul_YEO = 0x0ec5, + XK_Hangul_YE = 0x0ec6, + XK_Hangul_O = 0x0ec7, + XK_Hangul_WA = 0x0ec8, + XK_Hangul_WAE = 0x0ec9, + XK_Hangul_OE = 0x0eca, + XK_Hangul_YO = 0x0ecb, + XK_Hangul_U = 0x0ecc, + XK_Hangul_WEO = 0x0ecd, + XK_Hangul_WE = 0x0ece, + XK_Hangul_WI = 0x0ecf, + XK_Hangul_YU = 0x0ed0, + XK_Hangul_EU = 0x0ed1, + XK_Hangul_YI = 0x0ed2, + XK_Hangul_I = 0x0ed3, + XK_Hangul_J_Kiyeog = 0x0ed4, + XK_Hangul_J_SsangKiyeog = 0x0ed5, + XK_Hangul_J_KiyeogSios = 0x0ed6, + XK_Hangul_J_Nieun = 0x0ed7, + XK_Hangul_J_NieunJieuj = 0x0ed8, + XK_Hangul_J_NieunHieuh = 0x0ed9, + XK_Hangul_J_Dikeud = 0x0eda, + XK_Hangul_J_Rieul = 0x0edb, + XK_Hangul_J_RieulKiyeog = 0x0edc, + XK_Hangul_J_RieulMieum = 0x0edd, + XK_Hangul_J_RieulPieub = 0x0ede, + XK_Hangul_J_RieulSios = 0x0edf, + XK_Hangul_J_RieulTieut = 0x0ee0, + XK_Hangul_J_RieulPhieuf = 0x0ee1, + XK_Hangul_J_RieulHieuh = 0x0ee2, + XK_Hangul_J_Mieum = 0x0ee3, + XK_Hangul_J_Pieub = 0x0ee4, + XK_Hangul_J_PieubSios = 0x0ee5, + XK_Hangul_J_Sios = 0x0ee6, + XK_Hangul_J_SsangSios = 0x0ee7, + XK_Hangul_J_Ieung = 0x0ee8, + XK_Hangul_J_Jieuj = 0x0ee9, + XK_Hangul_J_Cieuc = 0x0eea, + XK_Hangul_J_Khieuq = 0x0eeb, + XK_Hangul_J_Tieut = 0x0eec, + XK_Hangul_J_Phieuf = 0x0eed, + XK_Hangul_J_Hieuh = 0x0eee, + XK_Hangul_RieulYeorinHieuh = 0x0eef, + XK_Hangul_SunkyeongeumMieum = 0x0ef0, + XK_Hangul_SunkyeongeumPieub = 0x0ef1, + XK_Hangul_PanSios = 0x0ef2, + XK_Hangul_KkogjiDalrinIeung = 0x0ef3, + XK_Hangul_SunkyeongeumPhieuf = 0x0ef4, + XK_Hangul_YeorinHieuh = 0x0ef5, + XK_Hangul_AraeA = 0x0ef6, + XK_Hangul_AraeAE = 0x0ef7, + XK_Hangul_J_PanSios = 0x0ef8, + XK_Hangul_J_KkogjiDalrinIeung = 0x0ef9, + XK_Hangul_J_YeorinHieuh = 0x0efa, + XK_Korean_Won = 0x0eff, /*(U+20A9 WON SIGN)*/ + XK_Armenian_ligature_ew = 0x1000587, /* U+0587 ARMENIAN SMALL LIGATURE ECH YIWN */ + XK_Armenian_full_stop = 0x1000589, /* U+0589 ARMENIAN FULL STOP */ + XK_Armenian_verjaket = 0x1000589, /* U+0589 ARMENIAN FULL STOP */ + XK_Armenian_separation_mark = 0x100055d, /* U+055D ARMENIAN COMMA */ + XK_Armenian_but = 0x100055d, /* U+055D ARMENIAN COMMA */ + XK_Armenian_hyphen = 0x100058a, /* U+058A ARMENIAN HYPHEN */ + XK_Armenian_yentamna = 0x100058a, /* U+058A ARMENIAN HYPHEN */ + XK_Armenian_exclam = 0x100055c, /* U+055C ARMENIAN EXCLAMATION MARK */ + XK_Armenian_amanak = 0x100055c, /* U+055C ARMENIAN EXCLAMATION MARK */ + XK_Armenian_accent = 0x100055b, /* U+055B ARMENIAN EMPHASIS MARK */ + XK_Armenian_shesht = 0x100055b, /* U+055B ARMENIAN EMPHASIS MARK */ + XK_Armenian_question = 0x100055e, /* U+055E ARMENIAN QUESTION MARK */ + XK_Armenian_paruyk = 0x100055e, /* U+055E ARMENIAN QUESTION MARK */ + XK_Armenian_AYB = 0x1000531, /* U+0531 ARMENIAN CAPITAL LETTER AYB */ + XK_Armenian_ayb = 0x1000561, /* U+0561 ARMENIAN SMALL LETTER AYB */ + XK_Armenian_BEN = 0x1000532, /* U+0532 ARMENIAN CAPITAL LETTER BEN */ + XK_Armenian_ben = 0x1000562, /* U+0562 ARMENIAN SMALL LETTER BEN */ + XK_Armenian_GIM = 0x1000533, /* U+0533 ARMENIAN CAPITAL LETTER GIM */ + XK_Armenian_gim = 0x1000563, /* U+0563 ARMENIAN SMALL LETTER GIM */ + XK_Armenian_DA = 0x1000534, /* U+0534 ARMENIAN CAPITAL LETTER DA */ + XK_Armenian_da = 0x1000564, /* U+0564 ARMENIAN SMALL LETTER DA */ + XK_Armenian_YECH = 0x1000535, /* U+0535 ARMENIAN CAPITAL LETTER ECH */ + XK_Armenian_yech = 0x1000565, /* U+0565 ARMENIAN SMALL LETTER ECH */ + XK_Armenian_ZA = 0x1000536, /* U+0536 ARMENIAN CAPITAL LETTER ZA */ + XK_Armenian_za = 0x1000566, /* U+0566 ARMENIAN SMALL LETTER ZA */ + XK_Armenian_E = 0x1000537, /* U+0537 ARMENIAN CAPITAL LETTER EH */ + XK_Armenian_e = 0x1000567, /* U+0567 ARMENIAN SMALL LETTER EH */ + XK_Armenian_AT = 0x1000538, /* U+0538 ARMENIAN CAPITAL LETTER ET */ + XK_Armenian_at = 0x1000568, /* U+0568 ARMENIAN SMALL LETTER ET */ + XK_Armenian_TO = 0x1000539, /* U+0539 ARMENIAN CAPITAL LETTER TO */ + XK_Armenian_to = 0x1000569, /* U+0569 ARMENIAN SMALL LETTER TO */ + XK_Armenian_ZHE = 0x100053a, /* U+053A ARMENIAN CAPITAL LETTER ZHE */ + XK_Armenian_zhe = 0x100056a, /* U+056A ARMENIAN SMALL LETTER ZHE */ + XK_Armenian_INI = 0x100053b, /* U+053B ARMENIAN CAPITAL LETTER INI */ + XK_Armenian_ini = 0x100056b, /* U+056B ARMENIAN SMALL LETTER INI */ + XK_Armenian_LYUN = 0x100053c, /* U+053C ARMENIAN CAPITAL LETTER LIWN */ + XK_Armenian_lyun = 0x100056c, /* U+056C ARMENIAN SMALL LETTER LIWN */ + XK_Armenian_KHE = 0x100053d, /* U+053D ARMENIAN CAPITAL LETTER XEH */ + XK_Armenian_khe = 0x100056d, /* U+056D ARMENIAN SMALL LETTER XEH */ + XK_Armenian_TSA = 0x100053e, /* U+053E ARMENIAN CAPITAL LETTER CA */ + XK_Armenian_tsa = 0x100056e, /* U+056E ARMENIAN SMALL LETTER CA */ + XK_Armenian_KEN = 0x100053f, /* U+053F ARMENIAN CAPITAL LETTER KEN */ + XK_Armenian_ken = 0x100056f, /* U+056F ARMENIAN SMALL LETTER KEN */ + XK_Armenian_HO = 0x1000540, /* U+0540 ARMENIAN CAPITAL LETTER HO */ + XK_Armenian_ho = 0x1000570, /* U+0570 ARMENIAN SMALL LETTER HO */ + XK_Armenian_DZA = 0x1000541, /* U+0541 ARMENIAN CAPITAL LETTER JA */ + XK_Armenian_dza = 0x1000571, /* U+0571 ARMENIAN SMALL LETTER JA */ + XK_Armenian_GHAT = 0x1000542, /* U+0542 ARMENIAN CAPITAL LETTER GHAD */ + XK_Armenian_ghat = 0x1000572, /* U+0572 ARMENIAN SMALL LETTER GHAD */ + XK_Armenian_TCHE = 0x1000543, /* U+0543 ARMENIAN CAPITAL LETTER CHEH */ + XK_Armenian_tche = 0x1000573, /* U+0573 ARMENIAN SMALL LETTER CHEH */ + XK_Armenian_MEN = 0x1000544, /* U+0544 ARMENIAN CAPITAL LETTER MEN */ + XK_Armenian_men = 0x1000574, /* U+0574 ARMENIAN SMALL LETTER MEN */ + XK_Armenian_HI = 0x1000545, /* U+0545 ARMENIAN CAPITAL LETTER YI */ + XK_Armenian_hi = 0x1000575, /* U+0575 ARMENIAN SMALL LETTER YI */ + XK_Armenian_NU = 0x1000546, /* U+0546 ARMENIAN CAPITAL LETTER NOW */ + XK_Armenian_nu = 0x1000576, /* U+0576 ARMENIAN SMALL LETTER NOW */ + XK_Armenian_SHA = 0x1000547, /* U+0547 ARMENIAN CAPITAL LETTER SHA */ + XK_Armenian_sha = 0x1000577, /* U+0577 ARMENIAN SMALL LETTER SHA */ + XK_Armenian_VO = 0x1000548, /* U+0548 ARMENIAN CAPITAL LETTER VO */ + XK_Armenian_vo = 0x1000578, /* U+0578 ARMENIAN SMALL LETTER VO */ + XK_Armenian_CHA = 0x1000549, /* U+0549 ARMENIAN CAPITAL LETTER CHA */ + XK_Armenian_cha = 0x1000579, /* U+0579 ARMENIAN SMALL LETTER CHA */ + XK_Armenian_PE = 0x100054a, /* U+054A ARMENIAN CAPITAL LETTER PEH */ + XK_Armenian_pe = 0x100057a, /* U+057A ARMENIAN SMALL LETTER PEH */ + XK_Armenian_JE = 0x100054b, /* U+054B ARMENIAN CAPITAL LETTER JHEH */ + XK_Armenian_je = 0x100057b, /* U+057B ARMENIAN SMALL LETTER JHEH */ + XK_Armenian_RA = 0x100054c, /* U+054C ARMENIAN CAPITAL LETTER RA */ + XK_Armenian_ra = 0x100057c, /* U+057C ARMENIAN SMALL LETTER RA */ + XK_Armenian_SE = 0x100054d, /* U+054D ARMENIAN CAPITAL LETTER SEH */ + XK_Armenian_se = 0x100057d, /* U+057D ARMENIAN SMALL LETTER SEH */ + XK_Armenian_VEV = 0x100054e, /* U+054E ARMENIAN CAPITAL LETTER VEW */ + XK_Armenian_vev = 0x100057e, /* U+057E ARMENIAN SMALL LETTER VEW */ + XK_Armenian_TYUN = 0x100054f, /* U+054F ARMENIAN CAPITAL LETTER TIWN */ + XK_Armenian_tyun = 0x100057f, /* U+057F ARMENIAN SMALL LETTER TIWN */ + XK_Armenian_RE = 0x1000550, /* U+0550 ARMENIAN CAPITAL LETTER REH */ + XK_Armenian_re = 0x1000580, /* U+0580 ARMENIAN SMALL LETTER REH */ + XK_Armenian_TSO = 0x1000551, /* U+0551 ARMENIAN CAPITAL LETTER CO */ + XK_Armenian_tso = 0x1000581, /* U+0581 ARMENIAN SMALL LETTER CO */ + XK_Armenian_VYUN = 0x1000552, /* U+0552 ARMENIAN CAPITAL LETTER YIWN */ + XK_Armenian_vyun = 0x1000582, /* U+0582 ARMENIAN SMALL LETTER YIWN */ + XK_Armenian_PYUR = 0x1000553, /* U+0553 ARMENIAN CAPITAL LETTER PIWR */ + XK_Armenian_pyur = 0x1000583, /* U+0583 ARMENIAN SMALL LETTER PIWR */ + XK_Armenian_KE = 0x1000554, /* U+0554 ARMENIAN CAPITAL LETTER KEH */ + XK_Armenian_ke = 0x1000584, /* U+0584 ARMENIAN SMALL LETTER KEH */ + XK_Armenian_O = 0x1000555, /* U+0555 ARMENIAN CAPITAL LETTER OH */ + XK_Armenian_o = 0x1000585, /* U+0585 ARMENIAN SMALL LETTER OH */ + XK_Armenian_FE = 0x1000556, /* U+0556 ARMENIAN CAPITAL LETTER FEH */ + XK_Armenian_fe = 0x1000586, /* U+0586 ARMENIAN SMALL LETTER FEH */ + XK_Armenian_apostrophe = 0x100055a, /* U+055A ARMENIAN APOSTROPHE */ + XK_Georgian_an = 0x10010d0, /* U+10D0 GEORGIAN LETTER AN */ + XK_Georgian_ban = 0x10010d1, /* U+10D1 GEORGIAN LETTER BAN */ + XK_Georgian_gan = 0x10010d2, /* U+10D2 GEORGIAN LETTER GAN */ + XK_Georgian_don = 0x10010d3, /* U+10D3 GEORGIAN LETTER DON */ + XK_Georgian_en = 0x10010d4, /* U+10D4 GEORGIAN LETTER EN */ + XK_Georgian_vin = 0x10010d5, /* U+10D5 GEORGIAN LETTER VIN */ + XK_Georgian_zen = 0x10010d6, /* U+10D6 GEORGIAN LETTER ZEN */ + XK_Georgian_tan = 0x10010d7, /* U+10D7 GEORGIAN LETTER TAN */ + XK_Georgian_in = 0x10010d8, /* U+10D8 GEORGIAN LETTER IN */ + XK_Georgian_kan = 0x10010d9, /* U+10D9 GEORGIAN LETTER KAN */ + XK_Georgian_las = 0x10010da, /* U+10DA GEORGIAN LETTER LAS */ + XK_Georgian_man = 0x10010db, /* U+10DB GEORGIAN LETTER MAN */ + XK_Georgian_nar = 0x10010dc, /* U+10DC GEORGIAN LETTER NAR */ + XK_Georgian_on = 0x10010dd, /* U+10DD GEORGIAN LETTER ON */ + XK_Georgian_par = 0x10010de, /* U+10DE GEORGIAN LETTER PAR */ + XK_Georgian_zhar = 0x10010df, /* U+10DF GEORGIAN LETTER ZHAR */ + XK_Georgian_rae = 0x10010e0, /* U+10E0 GEORGIAN LETTER RAE */ + XK_Georgian_san = 0x10010e1, /* U+10E1 GEORGIAN LETTER SAN */ + XK_Georgian_tar = 0x10010e2, /* U+10E2 GEORGIAN LETTER TAR */ + XK_Georgian_un = 0x10010e3, /* U+10E3 GEORGIAN LETTER UN */ + XK_Georgian_phar = 0x10010e4, /* U+10E4 GEORGIAN LETTER PHAR */ + XK_Georgian_khar = 0x10010e5, /* U+10E5 GEORGIAN LETTER KHAR */ + XK_Georgian_ghan = 0x10010e6, /* U+10E6 GEORGIAN LETTER GHAN */ + XK_Georgian_qar = 0x10010e7, /* U+10E7 GEORGIAN LETTER QAR */ + XK_Georgian_shin = 0x10010e8, /* U+10E8 GEORGIAN LETTER SHIN */ + XK_Georgian_chin = 0x10010e9, /* U+10E9 GEORGIAN LETTER CHIN */ + XK_Georgian_can = 0x10010ea, /* U+10EA GEORGIAN LETTER CAN */ + XK_Georgian_jil = 0x10010eb, /* U+10EB GEORGIAN LETTER JIL */ + XK_Georgian_cil = 0x10010ec, /* U+10EC GEORGIAN LETTER CIL */ + XK_Georgian_char = 0x10010ed, /* U+10ED GEORGIAN LETTER CHAR */ + XK_Georgian_xan = 0x10010ee, /* U+10EE GEORGIAN LETTER XAN */ + XK_Georgian_jhan = 0x10010ef, /* U+10EF GEORGIAN LETTER JHAN */ + XK_Georgian_hae = 0x10010f0, /* U+10F0 GEORGIAN LETTER HAE */ + XK_Georgian_he = 0x10010f1, /* U+10F1 GEORGIAN LETTER HE */ + XK_Georgian_hie = 0x10010f2, /* U+10F2 GEORGIAN LETTER HIE */ + XK_Georgian_we = 0x10010f3, /* U+10F3 GEORGIAN LETTER WE */ + XK_Georgian_har = 0x10010f4, /* U+10F4 GEORGIAN LETTER HAR */ + XK_Georgian_hoe = 0x10010f5, /* U+10F5 GEORGIAN LETTER HOE */ + XK_Georgian_fi = 0x10010f6, /* U+10F6 GEORGIAN LETTER FI */ + XK_Xabovedot = 0x1001e8a, /* U+1E8A LATIN CAPITAL LETTER X WITH DOT ABOVE */ + XK_Ibreve = 0x100012c, /* U+012C LATIN CAPITAL LETTER I WITH BREVE */ + XK_Zstroke = 0x10001b5, /* U+01B5 LATIN CAPITAL LETTER Z WITH STROKE */ + XK_Gcaron = 0x10001e6, /* U+01E6 LATIN CAPITAL LETTER G WITH CARON */ + XK_Ocaron = 0x10001d1, /* U+01D2 LATIN CAPITAL LETTER O WITH CARON */ + XK_Obarred = 0x100019f, /* U+019F LATIN CAPITAL LETTER O WITH MIDDLE TILDE */ + XK_xabovedot = 0x1001e8b, /* U+1E8B LATIN SMALL LETTER X WITH DOT ABOVE */ + XK_ibreve = 0x100012d, /* U+012D LATIN SMALL LETTER I WITH BREVE */ + XK_zstroke = 0x10001b6, /* U+01B6 LATIN SMALL LETTER Z WITH STROKE */ + XK_gcaron = 0x10001e7, /* U+01E7 LATIN SMALL LETTER G WITH CARON */ + XK_ocaron = 0x10001d2, /* U+01D2 LATIN SMALL LETTER O WITH CARON */ + XK_obarred = 0x1000275, /* U+0275 LATIN SMALL LETTER BARRED O */ + XK_SCHWA = 0x100018f, /* U+018F LATIN CAPITAL LETTER SCHWA */ + XK_schwa = 0x1000259, /* U+0259 LATIN SMALL LETTER SCHWA */ + XK_Lbelowdot = 0x1001e36, /* U+1E36 LATIN CAPITAL LETTER L WITH DOT BELOW */ + XK_lbelowdot = 0x1001e37, /* U+1E37 LATIN SMALL LETTER L WITH DOT BELOW */ + XK_Abelowdot = 0x1001ea0, /* U+1EA0 LATIN CAPITAL LETTER A WITH DOT BELOW */ + XK_abelowdot = 0x1001ea1, /* U+1EA1 LATIN SMALL LETTER A WITH DOT BELOW */ + XK_Ahook = 0x1001ea2, /* U+1EA2 LATIN CAPITAL LETTER A WITH HOOK ABOVE */ + XK_ahook = 0x1001ea3, /* U+1EA3 LATIN SMALL LETTER A WITH HOOK ABOVE */ + XK_Acircumflexacute = 0x1001ea4, /* U+1EA4 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE */ + XK_acircumflexacute = 0x1001ea5, /* U+1EA5 LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE */ + XK_Acircumflexgrave = 0x1001ea6, /* U+1EA6 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE */ + XK_acircumflexgrave = 0x1001ea7, /* U+1EA7 LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE */ + XK_Acircumflexhook = 0x1001ea8, /* U+1EA8 LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */ + XK_acircumflexhook = 0x1001ea9, /* U+1EA9 LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */ + XK_Acircumflextilde = 0x1001eaa, /* U+1EAA LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE */ + XK_acircumflextilde = 0x1001eab, /* U+1EAB LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE */ + XK_Acircumflexbelowdot = 0x1001eac, /* U+1EAC LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW */ + XK_acircumflexbelowdot = 0x1001ead, /* U+1EAD LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW */ + XK_Abreveacute = 0x1001eae, /* U+1EAE LATIN CAPITAL LETTER A WITH BREVE AND ACUTE */ + XK_abreveacute = 0x1001eaf, /* U+1EAF LATIN SMALL LETTER A WITH BREVE AND ACUTE */ + XK_Abrevegrave = 0x1001eb0, /* U+1EB0 LATIN CAPITAL LETTER A WITH BREVE AND GRAVE */ + XK_abrevegrave = 0x1001eb1, /* U+1EB1 LATIN SMALL LETTER A WITH BREVE AND GRAVE */ + XK_Abrevehook = 0x1001eb2, /* U+1EB2 LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE */ + XK_abrevehook = 0x1001eb3, /* U+1EB3 LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE */ + XK_Abrevetilde = 0x1001eb4, /* U+1EB4 LATIN CAPITAL LETTER A WITH BREVE AND TILDE */ + XK_abrevetilde = 0x1001eb5, /* U+1EB5 LATIN SMALL LETTER A WITH BREVE AND TILDE */ + XK_Abrevebelowdot = 0x1001eb6, /* U+1EB6 LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW */ + XK_abrevebelowdot = 0x1001eb7, /* U+1EB7 LATIN SMALL LETTER A WITH BREVE AND DOT BELOW */ + XK_Ebelowdot = 0x1001eb8, /* U+1EB8 LATIN CAPITAL LETTER E WITH DOT BELOW */ + XK_ebelowdot = 0x1001eb9, /* U+1EB9 LATIN SMALL LETTER E WITH DOT BELOW */ + XK_Ehook = 0x1001eba, /* U+1EBA LATIN CAPITAL LETTER E WITH HOOK ABOVE */ + XK_ehook = 0x1001ebb, /* U+1EBB LATIN SMALL LETTER E WITH HOOK ABOVE */ + XK_Etilde = 0x1001ebc, /* U+1EBC LATIN CAPITAL LETTER E WITH TILDE */ + XK_etilde = 0x1001ebd, /* U+1EBD LATIN SMALL LETTER E WITH TILDE */ + XK_Ecircumflexacute = 0x1001ebe, /* U+1EBE LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE */ + XK_ecircumflexacute = 0x1001ebf, /* U+1EBF LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE */ + XK_Ecircumflexgrave = 0x1001ec0, /* U+1EC0 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE */ + XK_ecircumflexgrave = 0x1001ec1, /* U+1EC1 LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE */ + XK_Ecircumflexhook = 0x1001ec2, /* U+1EC2 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */ + XK_ecircumflexhook = 0x1001ec3, /* U+1EC3 LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */ + XK_Ecircumflextilde = 0x1001ec4, /* U+1EC4 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE */ + XK_ecircumflextilde = 0x1001ec5, /* U+1EC5 LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE */ + XK_Ecircumflexbelowdot = 0x1001ec6, /* U+1EC6 LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW */ + XK_ecircumflexbelowdot = 0x1001ec7, /* U+1EC7 LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW */ + XK_Ihook = 0x1001ec8, /* U+1EC8 LATIN CAPITAL LETTER I WITH HOOK ABOVE */ + XK_ihook = 0x1001ec9, /* U+1EC9 LATIN SMALL LETTER I WITH HOOK ABOVE */ + XK_Ibelowdot = 0x1001eca, /* U+1ECA LATIN CAPITAL LETTER I WITH DOT BELOW */ + XK_ibelowdot = 0x1001ecb, /* U+1ECB LATIN SMALL LETTER I WITH DOT BELOW */ + XK_Obelowdot = 0x1001ecc, /* U+1ECC LATIN CAPITAL LETTER O WITH DOT BELOW */ + XK_obelowdot = 0x1001ecd, /* U+1ECD LATIN SMALL LETTER O WITH DOT BELOW */ + XK_Ohook = 0x1001ece, /* U+1ECE LATIN CAPITAL LETTER O WITH HOOK ABOVE */ + XK_ohook = 0x1001ecf, /* U+1ECF LATIN SMALL LETTER O WITH HOOK ABOVE */ + XK_Ocircumflexacute = 0x1001ed0, /* U+1ED0 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE */ + XK_ocircumflexacute = 0x1001ed1, /* U+1ED1 LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE */ + XK_Ocircumflexgrave = 0x1001ed2, /* U+1ED2 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE */ + XK_ocircumflexgrave = 0x1001ed3, /* U+1ED3 LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE */ + XK_Ocircumflexhook = 0x1001ed4, /* U+1ED4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */ + XK_ocircumflexhook = 0x1001ed5, /* U+1ED5 LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */ + XK_Ocircumflextilde = 0x1001ed6, /* U+1ED6 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE */ + XK_ocircumflextilde = 0x1001ed7, /* U+1ED7 LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE */ + XK_Ocircumflexbelowdot = 0x1001ed8, /* U+1ED8 LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW */ + XK_ocircumflexbelowdot = 0x1001ed9, /* U+1ED9 LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW */ + XK_Ohornacute = 0x1001eda, /* U+1EDA LATIN CAPITAL LETTER O WITH HORN AND ACUTE */ + XK_ohornacute = 0x1001edb, /* U+1EDB LATIN SMALL LETTER O WITH HORN AND ACUTE */ + XK_Ohorngrave = 0x1001edc, /* U+1EDC LATIN CAPITAL LETTER O WITH HORN AND GRAVE */ + XK_ohorngrave = 0x1001edd, /* U+1EDD LATIN SMALL LETTER O WITH HORN AND GRAVE */ + XK_Ohornhook = 0x1001ede, /* U+1EDE LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE */ + XK_ohornhook = 0x1001edf, /* U+1EDF LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE */ + XK_Ohorntilde = 0x1001ee0, /* U+1EE0 LATIN CAPITAL LETTER O WITH HORN AND TILDE */ + XK_ohorntilde = 0x1001ee1, /* U+1EE1 LATIN SMALL LETTER O WITH HORN AND TILDE */ + XK_Ohornbelowdot = 0x1001ee2, /* U+1EE2 LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW */ + XK_ohornbelowdot = 0x1001ee3, /* U+1EE3 LATIN SMALL LETTER O WITH HORN AND DOT BELOW */ + XK_Ubelowdot = 0x1001ee4, /* U+1EE4 LATIN CAPITAL LETTER U WITH DOT BELOW */ + XK_ubelowdot = 0x1001ee5, /* U+1EE5 LATIN SMALL LETTER U WITH DOT BELOW */ + XK_Uhook = 0x1001ee6, /* U+1EE6 LATIN CAPITAL LETTER U WITH HOOK ABOVE */ + XK_uhook = 0x1001ee7, /* U+1EE7 LATIN SMALL LETTER U WITH HOOK ABOVE */ + XK_Uhornacute = 0x1001ee8, /* U+1EE8 LATIN CAPITAL LETTER U WITH HORN AND ACUTE */ + XK_uhornacute = 0x1001ee9, /* U+1EE9 LATIN SMALL LETTER U WITH HORN AND ACUTE */ + XK_Uhorngrave = 0x1001eea, /* U+1EEA LATIN CAPITAL LETTER U WITH HORN AND GRAVE */ + XK_uhorngrave = 0x1001eeb, /* U+1EEB LATIN SMALL LETTER U WITH HORN AND GRAVE */ + XK_Uhornhook = 0x1001eec, /* U+1EEC LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE */ + XK_uhornhook = 0x1001eed, /* U+1EED LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE */ + XK_Uhorntilde = 0x1001eee, /* U+1EEE LATIN CAPITAL LETTER U WITH HORN AND TILDE */ + XK_uhorntilde = 0x1001eef, /* U+1EEF LATIN SMALL LETTER U WITH HORN AND TILDE */ + XK_Uhornbelowdot = 0x1001ef0, /* U+1EF0 LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW */ + XK_uhornbelowdot = 0x1001ef1, /* U+1EF1 LATIN SMALL LETTER U WITH HORN AND DOT BELOW */ + XK_Ybelowdot = 0x1001ef4, /* U+1EF4 LATIN CAPITAL LETTER Y WITH DOT BELOW */ + XK_ybelowdot = 0x1001ef5, /* U+1EF5 LATIN SMALL LETTER Y WITH DOT BELOW */ + XK_Yhook = 0x1001ef6, /* U+1EF6 LATIN CAPITAL LETTER Y WITH HOOK ABOVE */ + XK_yhook = 0x1001ef7, /* U+1EF7 LATIN SMALL LETTER Y WITH HOOK ABOVE */ + XK_Ytilde = 0x1001ef8, /* U+1EF8 LATIN CAPITAL LETTER Y WITH TILDE */ + XK_ytilde = 0x1001ef9, /* U+1EF9 LATIN SMALL LETTER Y WITH TILDE */ + XK_Ohorn = 0x10001a0, /* U+01A0 LATIN CAPITAL LETTER O WITH HORN */ + XK_ohorn = 0x10001a1, /* U+01A1 LATIN SMALL LETTER O WITH HORN */ + XK_Uhorn = 0x10001af, /* U+01AF LATIN CAPITAL LETTER U WITH HORN */ + XK_uhorn = 0x10001b0, /* U+01B0 LATIN SMALL LETTER U WITH HORN */ + XK_EcuSign = 0x10020a0, /* U+20A0 EURO-CURRENCY SIGN */ + XK_ColonSign = 0x10020a1, /* U+20A1 COLON SIGN */ + XK_CruzeiroSign = 0x10020a2, /* U+20A2 CRUZEIRO SIGN */ + XK_FFrancSign = 0x10020a3, /* U+20A3 FRENCH FRANC SIGN */ + XK_LiraSign = 0x10020a4, /* U+20A4 LIRA SIGN */ + XK_MillSign = 0x10020a5, /* U+20A5 MILL SIGN */ + XK_NairaSign = 0x10020a6, /* U+20A6 NAIRA SIGN */ + XK_PesetaSign = 0x10020a7, /* U+20A7 PESETA SIGN */ + XK_RupeeSign = 0x10020a8, /* U+20A8 RUPEE SIGN */ + XK_WonSign = 0x10020a9, /* U+20A9 WON SIGN */ + XK_NewSheqelSign = 0x10020aa, /* U+20AA NEW SHEQEL SIGN */ + XK_DongSign = 0x10020ab, /* U+20AB DONG SIGN */ + XK_EuroSign = 0x20ac, /* U+20AC EURO SIGN */ } diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 207b3f6bc..b5365e73d 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -9,13 +9,49 @@ foreign xlib { foreign import xcursor "system:Xcursor" @(default_calling_convention="c", link_prefix="X") foreign xcursor { - cursorGetTheme :: proc(display: ^Display) -> cstring --- - cursorGetDefaultSize :: proc(display: ^Display) -> i32 --- - cursorLibraryLoadImage :: proc(name: cstring, theme: cstring, size: i32) -> rawptr --- - cursorImageLoadCursor :: proc(display: ^Display, img: rawptr) -> Cursor --- - cursorImageDestroy :: proc(img: rawptr) --- + cursorGetTheme :: proc(display: ^Display) -> cstring --- + cursorGetDefaultSize :: proc(display: ^Display) -> i32 --- + cursorLibraryLoadCursor :: proc(display: ^Display, name: cstring) -> Cursor --- + cursorLibraryLoadImage :: proc(name: cstring, theme: cstring, size: i32) -> rawptr --- + cursorImageLoadCursor :: proc(display: ^Display, img: rawptr) -> Cursor --- + cursorImageDestroy :: proc(img: rawptr) --- } +foreign import xfixes "system:Xfixes" +@(default_calling_convention="c", link_prefix="XFixes") +foreign xfixes { + HideCursor :: proc(display: ^Display, window: Window) --- + ShowCursor :: proc(display: ^Display, window: Window) --- +} + +foreign import xrandr "system:Xrandr" +@(default_calling_convention="c") +foreign xrandr { + XRRSizes :: proc(display: ^Display, screen: i32, nsizes: ^i32) -> [^]XRRScreenSize --- + XRRGetScreenResources :: proc(display: ^Display, window: Window) -> ^XRRScreenResources --- + XRRFreeScreenResources :: proc(resources: ^XRRScreenResources) --- + XRRGetOutputInfo :: proc(display: ^Display, resources: ^XRRScreenResources, output: RROutput) -> ^XRROutputInfo --- + XRRFreeOutputInfo :: proc(output_info: ^XRROutputInfo) --- + XRRGetCrtcInfo :: proc(display: ^Display, resources: ^XRRScreenResources, crtc: RRCrtc) -> ^XRRCrtcInfo --- + XRRFreeCrtcInfo :: proc(crtc_info: ^XRRCrtcInfo) --- + XRRGetMonitors :: proc(dpy: ^Display, window: Window, get_active: b32, nmonitors: ^i32) -> [^]XRRMonitorInfo --- +} + +foreign import xinput "system:Xi" +foreign xinput { + XISelectEvents :: proc(display: ^Display, window: Window, masks: [^]XIEventMask, num_masks: i32) -> i32 --- + XIQueryVersion :: proc(display: ^Display, major: ^i32, minor: ^i32) -> Status --- +} + +XISetMask :: proc(ptr: [^]u8, event: XIEventType) { + ptr[cast(i32)event >> 3] |= (1 << cast(uint)((cast(i32)event) & 7)) +} + +XIMaskIsSet :: proc(ptr: [^]u8, event: i32) -> bool { + return (ptr[event >> 3] & (1 << cast(uint)((event) & 7))) != 0 +} + + /* ---- X11/Xlib.h ---------------------------------------------------------*/ @(default_calling_convention="c", link_prefix="X") diff --git a/vendor/x11/xlib/xlib_types.odin b/vendor/x11/xlib/xlib_types.odin index d2bd2c5a3..5344d9aae 100644 --- a/vendor/x11/xlib/xlib_types.odin +++ b/vendor/x11/xlib/xlib_types.odin @@ -26,6 +26,7 @@ GContext :: XID RRCrtc :: XID RROutput :: XID +RRMode :: XID KeyCode :: u8 @@ -46,9 +47,9 @@ XExtCodes :: struct { } XPixmapFormatValues :: struct { - depth: i32, - bits_per_pixel: i32, - scanline_pad: i32, + depth: i32, + bits_per_pixel: i32, + scanline_pad: i32, } XGCValues :: struct { @@ -129,47 +130,47 @@ ScreenFormat :: struct { } XSetWindowAttributes :: struct { - background_pixmap: Pixmap, - background_pixel: uint, - border_pixmap: Pixmap, - border_pixel: uint, - bit_gravity: Gravity, - win_gravity: Gravity, - backing_store: BackingStore, - backing_planes: uint, - backing_pixel: uint, - save_under: b32, - event_mask: EventMask, - do_not_propagate_mask: EventMask, - override_redirect: b32, - colormap: Colormap, - cursor: Cursor, + background_pixmap: Pixmap, + background_pixel: uint, + border_pixmap: Pixmap, + border_pixel: uint, + bit_gravity: Gravity, + win_gravity: Gravity, + backing_store: BackingStore, + backing_planes: uint, + backing_pixel: uint, + save_under: b32, + event_mask: EventMask, + do_not_propagate_mask: EventMask, + override_redirect: b32, + colormap: Colormap, + cursor: Cursor, } XWindowAttributes :: struct { - x: i32, - y: i32, - width: i32, - height: i32, - border_width: i32, - depth: i32, - visual: ^Visual, - root: Window, - class: WindowClass, - bit_gravity: Gravity, - win_gravity: Gravity, - backing_store: BackingStore, - backing_planes: uint, - backing_pixel: uint, - save_under: b32, - colormap: Colormap, - map_installed: b32, - map_state: WindowMapState, - all_event_masks: EventMask, - your_event_mask: EventMask, - do_not_propagate_mask: EventMask, - override_redirect: b32, - screen: ^Screen, + x: i32, + y: i32, + width: i32, + height: i32, + border_width: i32, + depth: i32, + visual: ^Visual, + root: Window, + class: WindowClass, + bit_gravity: Gravity, + win_gravity: Gravity, + backing_store: BackingStore, + backing_planes: uint, + backing_pixel: uint, + save_under: b32, + colormap: Colormap, + map_installed: b32, + map_state: WindowMapState, + all_event_masks: EventMask, + your_event_mask: EventMask, + do_not_propagate_mask: EventMask, + override_redirect: b32, + screen: ^Screen, } XHostAddress :: struct { @@ -186,50 +187,50 @@ XServerInterpretedAddress :: struct { } XImage :: struct { - width: i32, - height: i32, - xoffset: i32, - format: ImageFormat, - data: rawptr, - byte_order: i32, - bitmap_unit: i32, - bitmap_bit_order: ByteOrder, - bitmap_pad: i32, - depth: i32, - bytes_per_line: i32, - bits_per_pixel: i32, - red_mask: uint, - green_mask: uint, - blue_mask: uint, - obdata: rawptr, - f: struct { - create_image: proc "c" ( - display: ^Display, - visual: ^Visual, - depth: u32, - format: i32, - offset: i32, - data: rawptr, - width: u32, - height: u32, - pad: i32, - stride: i32) -> ^XImage, - destroy_image: proc "c" (image: ^XImage) -> i32, - get_pixel: proc "c" (image: ^XImage) -> uint, - put_pixel: proc "c" (image: ^XImage, x: i32, y: i32, pixel: uint) -> i32, - sub_image: proc "c" (image: ^XImage, x: i32, y: i32, w: u32, h: u32) -> ^XImage, - add_pixel: proc "c" (image: ^XImage, val: int) -> i32, + width: i32, + height: i32, + xoffset: i32, + format: ImageFormat, + data: rawptr, + byte_order: i32, + bitmap_unit: i32, + bitmap_bit_order: ByteOrder, + bitmap_pad: i32, + depth: i32, + bytes_per_line: i32, + bits_per_pixel: i32, + red_mask: uint, + green_mask: uint, + blue_mask: uint, + obdata: rawptr, + f: struct { + create_image: proc "c" ( + display: ^Display, + visual: ^Visual, + depth: u32, + format: i32, + offset: i32, + data: rawptr, + width: u32, + height: u32, + pad: i32, + stride: i32) -> ^XImage, + destroy_image: proc "c" (image: ^XImage) -> i32, + get_pixel: proc "c" (image: ^XImage) -> uint, + put_pixel: proc "c" (image: ^XImage, x: i32, y: i32, pixel: uint) -> i32, + sub_image: proc "c" (image: ^XImage, x: i32, y: i32, w: u32, h: u32) -> ^XImage, + add_pixel: proc "c" (image: ^XImage, val: int) -> i32, }, } XWindowChanges :: struct { - x: i32, - y: i32, - width: i32, - height: i32, - border_width: i32, - sibling: Window, - stack_mode: WindowStacking, + x: i32, + y: i32, + width: i32, + height: i32, + border_width: i32, + sibling: Window, + stack_mode: WindowStacking, } XColor :: struct { @@ -242,42 +243,42 @@ XColor :: struct { } XSegment :: struct { - x1: i16, - y1: i16, - x2: i16, - y2: i16, + x1: i16, + y1: i16, + x2: i16, + y2: i16, } XPoint :: struct { - x: i16, - y: i16, + x: i16, + y: i16, } XRectangle :: struct { - x: i16, - y: i16, - width: u16, - height: u16, + x: i16, + y: i16, + width: u16, + height: u16, } XArc :: struct { - x: i16, - y: i16, - width: u16, - height: u16, - angle1: i16, - angle2: i16, + x: i16, + y: i16, + width: u16, + height: u16, + angle1: i16, + angle2: i16, } XKeyboardControl :: struct { - key_click_percent: i32, - bell_percent: i32, - bell_pitch: i32, - bell_duration: i32, - led: i32, - led_mode: KeyboardLedMode, - key: i32, - auto_repeat_mode: KeyboardAutoRepeatMode, + key_click_percent: i32, + bell_percent: i32, + bell_pitch: i32, + bell_duration: i32, + led: i32, + led_mode: KeyboardLedMode, + key: i32, + auto_repeat_mode: KeyboardAutoRepeatMode, } XKeyboardState :: struct { @@ -699,23 +700,23 @@ XAnyEvent :: struct { } XGenericEvent :: struct { - type: EventType, - serial: uint, - send_event: b32, - display: ^Display, - extension: i32, - evtype: i32, + type: EventType, + serial: uint, + send_event: b32, + display: ^Display, + extension: i32, + evtype: i32, } XGenericEventCookie :: struct { - type: EventType, - serial: uint, - send_event: b32, - display: ^Display, - extension: i32, - evtype: i32, - cookie: u32, - data: rawptr, + type: EventType, + serial: uint, + send_event: b32, + display: ^Display, + extension: i32, + evtype: i32, + cookie: u32, + data: rawptr, } XEvent :: struct #raw_union { @@ -757,55 +758,55 @@ XEvent :: struct #raw_union { } XCharStruct :: struct { - lbearing: i16, - rbearing: i16, - width: i16, - ascent: i16, - descent: i16, - attributes: u16, + lbearing: i16, + rbearing: i16, + width: i16, + ascent: i16, + descent: i16, + attributes: u16, } XFontProp :: struct { - name: Atom, - card32: uint, + name: Atom, + card32: uint, } XFontStruct :: struct { - ext_data: ^XExtData, - fid: Font, - direction: u32, - min_char_or_byte2: u32, - max_char_or_byte2: u32, - min_byte1: u32, - max_byte1: u32, - all_chars_exist: i32, - default_char: u32, - n_properties: i32, - properties: ^XFontProp, - min_bounds: XCharStruct, - max_bounds: XCharStruct, - per_char: ^XCharStruct, - ascent: i32, - descent: i32, + ext_data: ^XExtData, + fid: Font, + direction: u32, + min_char_or_byte2: u32, + max_char_or_byte2: u32, + min_byte1: u32, + max_byte1: u32, + all_chars_exist: i32, + default_char: u32, + n_properties: i32, + properties: ^XFontProp, + min_bounds: XCharStruct, + max_bounds: XCharStruct, + per_char: ^XCharStruct, + ascent: i32, + descent: i32, } XTextItem :: struct { - chars: [^]u8, - nchars: i32, - delta: i32, - font: Font, + chars: [^]u8, + nchars: i32, + delta: i32, + font: Font, } XChar2b :: struct { - byte1: u8, - byte2: u8, + byte1: u8, + byte2: u8, } XTextItem16 :: struct { - chars: ^XChar2b, - nchars: i32, - delta: i32, - font: Font, + chars: ^XChar2b, + nchars: i32, + delta: i32, + font: Font, } XEDataObject :: struct #raw_union { @@ -818,8 +819,8 @@ XEDataObject :: struct #raw_union { } XFontSetExtents :: struct { - max_ink_extent: XRectangle, - max_logical_extent: XRectangle, + max_ink_extent: XRectangle, + max_logical_extent: XRectangle, } XOM :: distinct rawptr @@ -827,41 +828,41 @@ XOC :: distinct rawptr XFontSet :: XOC XmbTextItem :: struct { - chars: [^]u8, - nchars: i32, - delta: i32, - font_set: XFontSet, + chars: [^]u8, + nchars: i32, + delta: i32, + font_set: XFontSet, } XwcTextItem :: struct { - chars: [^]rune, - nchars: i32, - delta: i32, - font_set: XFontSet, + chars: [^]rune, + nchars: i32, + delta: i32, + font_set: XFontSet, } XOMCharSetList :: struct { - charset_count: i32, - charset_list: [^]cstring, + charset_count: i32, + charset_list: [^]cstring, } XOrientation :: enum i32 { - XOMOrientation_LTR_TTB = 0, - XOMOrientation_RTL_TTB = 1, - XOMOrientation_TTB_LTR = 2, - XOMOrientation_TTB_RTL = 3, - XOMOrientation_Context = 4, + XOMOrientation_LTR_TTB = 0, + XOMOrientation_RTL_TTB = 1, + XOMOrientation_TTB_LTR = 2, + XOMOrientation_TTB_RTL = 3, + XOMOrientation_Context = 4, } XOMOrientation :: struct { - num_orientation: i32, - orientation: [^]XOrientation, + num_orientation: i32, + orientation: [^]XOrientation, } XOMFontInfo :: struct { - num_font: i32, - font_struct_list: [^]^XFontStruct, - font_name_list: [^]cstring, + num_font: i32, + font_struct_list: [^]^XFontStruct, + font_name_list: [^]cstring, } XIM :: distinct rawptr @@ -874,38 +875,38 @@ XIDProc :: #type proc "c" (xim: XIM, client_data: rawptr, call_data: rawptr) XIMStyle :: uint XIMStyles :: struct { - count_styles: u16, - supported_styles: [^]XIMStyle, + count_styles: u16, + supported_styles: [^]XIMStyle, } XVaNestedList :: distinct rawptr XIMCallback :: struct { - client_data: rawptr, - callback: XIMProc, + client_data: rawptr, + callback: XIMProc, } XICCallback :: struct { - client_data: rawptr, - callback: XICProc, + client_data: rawptr, + callback: XICProc, } XIMFeedback :: uint XIMText :: struct { - length: u16, - feedback: ^XIMFeedback, - encoding_is_wchar: b32, - string: struct #raw_union { + length: u16, + feedback: ^XIMFeedback, + encoding_is_wchar: b32, + string: struct #raw_union { multi_byte: [^]u8, wide_char: [^]rune, - }, + }, } XIMPreeditState :: uint XIMPreeditStateNotifyCallbackStruct :: struct { - state: XIMPreeditState, + state: XIMPreeditState, } XIMResetState :: uint @@ -913,13 +914,13 @@ XIMResetState :: uint XIMStringConversionFeedback :: uint XIMStringConversionText :: struct { - length: u16, - feedback: ^XIMStringConversionFeedback, - encoding_is_wchar: b32, - string: struct #raw_union { + length: u16, + feedback: ^XIMStringConversionFeedback, + encoding_is_wchar: b32, + string: struct #raw_union { mbs: [^]u8, wcs: [^]rune, - }, + }, } XIMStringConversionPosition :: u16 @@ -927,76 +928,76 @@ XIMStringConversionType :: u16 XIMStringConversionOperation :: u16 XIMCaretDirection :: enum i32 { - XIMForwardChar = 0, - XIMBackwardChar = 1, - XIMForwardWord = 2, - XIMBackwardWord = 3, - XIMCaretUp = 4, - XIMCaretDown = 5, - XIMNextLine = 6, - XIMPreviousLine = 7, - XIMLineStart = 8, - XIMLineEnd = 9, - XIMAbsolutePosition = 10, - XIMDontChang = 11, + XIMForwardChar = 0, + XIMBackwardChar = 1, + XIMForwardWord = 2, + XIMBackwardWord = 3, + XIMCaretUp = 4, + XIMCaretDown = 5, + XIMNextLine = 6, + XIMPreviousLine = 7, + XIMLineStart = 8, + XIMLineEnd = 9, + XIMAbsolutePosition = 10, + XIMDontChang = 11, } XIMStringConversionCallbackStruct :: struct { - position: XIMStringConversionPosition, - direction: XIMCaretDirection, - operation: XIMStringConversionOperation, - factor: u16, - text: ^XIMStringConversionText, + position: XIMStringConversionPosition, + direction: XIMCaretDirection, + operation: XIMStringConversionOperation, + factor: u16, + text: ^XIMStringConversionText, } XIMPreeditDrawCallbackStruct :: struct { - caret: i32, - chg_first: i32, - chg_length: i32, - text: ^XIMText, + caret: i32, + chg_first: i32, + chg_length: i32, + text: ^XIMText, } XIMCaretStyle :: enum i32 { - XIMIsInvisible, - XIMIsPrimary, - XIMIsSecondary, + XIMIsInvisible, + XIMIsPrimary, + XIMIsSecondary, } XIMPreeditCaretCallbackStruct :: struct { - position: i32, - direction: XIMCaretDirection, - style: XIMCaretStyle, + position: i32, + direction: XIMCaretDirection, + style: XIMCaretStyle, } XIMStatusDataType :: enum { - XIMTextType, - XIMBitmapType, + XIMTextType, + XIMBitmapType, } XIMStatusDrawCallbackStruct :: struct { - type: XIMStatusDataType, - data: struct #raw_union { + type: XIMStatusDataType, + data: struct #raw_union { text: ^XIMText, bitmap: Pixmap, - }, + }, } XIMHotKeyTrigger :: struct { - keysym: KeySym, - modifier: i32, - modifier_mask: i32, + keysym: KeySym, + modifier: i32, + modifier_mask: i32, } XIMHotKeyTriggers :: struct { - num_hot_key: i32, - key: [^]XIMHotKeyTrigger, + num_hot_key: i32, + key: [^]XIMHotKeyTrigger, } XIMHotKeyState :: uint XIMValuesList :: struct { - count_values: u16, - supported_values: [^]cstring, + count_values: u16, + supported_values: [^]cstring, } XConnectionWatchProc :: #type proc "c" ( @@ -1774,62 +1775,62 @@ XcmsColorFormat :: uint XcmsFloat :: f64 XcmsRGB :: struct { - red: u16, - green: u16, - blue: u16, + red: u16, + green: u16, + blue: u16, } XcmsRGBi :: struct { - red: XcmsFloat, - green: XcmsFloat, - blue: XcmsFloat, + red: XcmsFloat, + green: XcmsFloat, + blue: XcmsFloat, } XcmsCIEXYZ :: struct { - X: XcmsFloat, - Y: XcmsFloat, - Z: XcmsFloat, + X: XcmsFloat, + Y: XcmsFloat, + Z: XcmsFloat, } XcmsCIEuvY :: struct { - u_prime: XcmsFloat, - v_prime: XcmsFloat, - Y: XcmsFloat, + u_prime: XcmsFloat, + v_prime: XcmsFloat, + Y: XcmsFloat, } XcmsCIExyY :: struct { - x: XcmsFloat, - y: XcmsFloat, - Y: XcmsFloat, + x: XcmsFloat, + y: XcmsFloat, + Y: XcmsFloat, } XcmsCIELab :: struct { - L_star: XcmsFloat, - a_star: XcmsFloat, - b_star: XcmsFloat, + L_star: XcmsFloat, + a_star: XcmsFloat, + b_star: XcmsFloat, } XcmsCIELuv :: struct { - L_star: XcmsFloat, - u_star: XcmsFloat, - v_star: XcmsFloat, + L_star: XcmsFloat, + u_star: XcmsFloat, + v_star: XcmsFloat, } XcmsTekHVC :: struct { - H: XcmsFloat, - V: XcmsFloat, - C: XcmsFloat, + H: XcmsFloat, + V: XcmsFloat, + C: XcmsFloat, } XcmsPad :: struct { - _: XcmsFloat, - _: XcmsFloat, - _: XcmsFloat, - _: XcmsFloat, + _: XcmsFloat, + _: XcmsFloat, + _: XcmsFloat, + _: XcmsFloat, } XcmsColor :: struct { - spec: struct #raw_union { + spec: struct #raw_union { RGB: XcmsRGB, RGBi: XcmsRGBi, CIEXYZ: XcmsCIEXYZ, @@ -1839,17 +1840,17 @@ XcmsColor :: struct { CIELuv: XcmsCIELuv, TekHVC: XcmsTekHVC, _: XcmsPad, - }, - pixel: uint, - format: XcmsColorFormat, + }, + pixel: uint, + format: XcmsColorFormat, } XcmsPerScrnInfo :: struct { - screenWhitePt: XcmsColor, - functionSet: rawptr, - screenData: rawptr, - state: u8, - _: [3]u8, + screenWhitePt: XcmsColor, + functionSet: rawptr, + screenData: rawptr, + state: u8, + _: [3]u8, } XcmsCCC :: distinct rawptr @@ -1871,15 +1872,15 @@ XcmsWhiteAdjustProc :: #type proc "c" ( compression: [^]b32) -> Status XcmsCCCRec :: struct { - dpy: ^Display, - screenNumber: i32, - visual: ^Visual, - clientWhitePt: XcmsColor, - gamutCompProc: XcmsCompressionProc, - gamutCompClientData: rawptr, - whitePtAdjProc: XcmsWhiteAdjustProc, - whitePtAdjClientData: rawptr, - pPerScrnInfo: ^XcmsPerScrnInfo, + dpy: ^Display, + screenNumber: i32, + visual: ^Visual, + clientWhitePt: XcmsColor, + gamutCompProc: XcmsCompressionProc, + gamutCompClientData: rawptr, + whitePtAdjProc: XcmsWhiteAdjustProc, + whitePtAdjClientData: rawptr, + pPerScrnInfo: ^XcmsPerScrnInfo, } XcmsScreenInitProc :: #type proc "c" ( @@ -1908,18 +1909,18 @@ XcmsFuncListPtr :: [^]XcmsConversionProc XcmsParseStringProc :: #type proc "c" (color_string: cstring, color: ^XcmsColor) -> i32 XcmsColorSpace :: struct { - prefix: cstring, - id: XcmsColorFormat, - parseString: XcmsParseStringProc, - to_CIEXYZ: XcmsFuncListPtr, - from_CIEXYZ: XcmsFuncListPtr, - inverse_flag: i32, + prefix: cstring, + id: XcmsColorFormat, + parseString: XcmsParseStringProc, + to_CIEXYZ: XcmsFuncListPtr, + from_CIEXYZ: XcmsFuncListPtr, + inverse_flag: i32, } XcmsFunctionSet :: struct { - DDColorSpaces: [^]^XcmsColorSpace, - screenInitProc: XcmsScreenInitProc, - screenFreeProc: XcmsScreenFreeProc, + DDColorSpaces: [^]^XcmsColorSpace, + screenInitProc: XcmsScreenInitProc, + screenFreeProc: XcmsScreenFreeProc, } @@ -1957,18 +1958,18 @@ XWMHints :: struct { } XTextProperty :: struct { - value: [^]u8, - encoding: Atom, - format: int, - nitems: uint, + value: [^]u8, + encoding: Atom, + format: int, + nitems: uint, } XICCEncodingStyle :: enum i32 { - XStringStyle, - XCompoundTextStyle, - XTextStyle, - XStdICCTextStyle, - XUTF8StringStyle, + XStringStyle, + XCompoundTextStyle, + XTextStyle, + XStdICCTextStyle, + XUTF8StringStyle, } XIconSize :: struct { @@ -1986,8 +1987,8 @@ XClassHint :: struct { } XComposeStatus :: struct { - compose_ptr: rawptr, - chars_matched: i32, + compose_ptr: rawptr, + chars_matched: i32, } Region :: distinct rawptr @@ -2040,8 +2041,8 @@ XrmClassList :: XrmQuarkList XrmRepresentation :: XrmQuark XrmValue :: struct { - size: u32, - addr: rawptr, + size: u32, + addr: rawptr, } XrmValuePtr :: [^]XrmValue @@ -2051,21 +2052,208 @@ XrmSearchList :: [^]XrmHashTable XrmDatabase :: distinct rawptr XrmOptionKind :: enum { - XrmoptionNoArg, - XrmoptionIsArg, - XrmoptionStickyArg, - XrmoptionSepArg, - XrmoptionResArg, - XrmoptionSkipArg, - XrmoptionSkipLine, - XrmoptionSkipNArgs, + XrmoptionNoArg, + XrmoptionIsArg, + XrmoptionStickyArg, + XrmoptionSepArg, + XrmoptionResArg, + XrmoptionSkipArg, + XrmoptionSkipLine, + XrmoptionSkipNArgs, } XrmOptionDescRec :: struct { - option: cstring, - specifier: cstring, - argKind: XrmOptionKind, - value: rawptr, + option: cstring, + specifier: cstring, + argKind: XrmOptionKind, + value: rawptr, } XrmOptionDescList :: [^]XrmOptionDescRec + +/* ---- X11/extensions/Xrandr.h ---------------------------------------------------------*/ + +XRRModeFlags :: bit_set[XRRModeBits;u64] +XRRModeBits :: enum u8 { + RR_HSyncPositive = 0, + RR_HSyncNegative = 1, + RR_VSyncPositive = 2, + RR_VSyncNegative = 3, + RR_Interlace = 4, + RR_DoubleScan = 5, + RR_CSync = 6, + RR_CSyncPositive = 7, + RR_CSyncNegative = 8, + RR_HSkewPresent = 9, + RR_BCast = 10, + RR_PixelMultiplex = 11, + RR_DoubleClock = 12, + RR_ClockDivideBy2 = 13, +} + +Rotation :: enum u16 { + Rotate_0 = 1, + Rotate_90 = 2, + Rotate_180 = 4, + Rotate_270 = 8, +} + +Connection :: enum u16 { + RR_Connected = 0, + RR_Disconnected = 1, + RR_UnknownConnection = 2, +} + +SubpixelOrder :: enum u16 { + Unknown = 0, + HorizontalRGB = 1, + HorizontalBGR = 2, + VerticalRGB = 3, + VerticalBGR = 4, + None = 5, +} + +XRRModeInfo :: struct { + id: RRMode, + width: u32, + height: u32, + dotClock: u64, + hSyncStart: u32, + hSyncEnd: u32, + hTotal: u32, + hSkew: u32, + vSyncStart: u32, + vSyncEnd: u32, + vTotal: u32, + name: cstring, + nameLength: u32, + modeFlags: XRRModeFlags, +} + +XRRScreenSize :: struct { + width, height: i32, + mwidth, mheight: i32, +} + +XRRScreenResources :: struct { + timestamp: Time, + configTimestamp: Time, + ncrtc: i32, + crtcs: [^]RRCrtc, + noutput: i32, + outputs: [^]RROutput, + nmode: i32, + modes: [^]XRRModeInfo, +} + +XRROutputInfo :: struct { + timestamp: Time, + crtc: RRCrtc, + name: cstring, + nameLen: i32, + mm_width: u64, + mm_height: u64, + connection: Connection, + subpixel_order: SubpixelOrder, + ncrtc: i32, + crtcs: [^]RRCrtc, + nclone: i32, + clones: [^]RROutput, + nmode: i32, + npreferred: i32, + modes: [^]RRMode, +} + +XRRCrtcInfo :: struct { + timestamp: Time, + x, y: i32, + width, height: u32, + mode: RRMode, + rotation: Rotation, + noutput: i32, + outputs: [^]RROutput, + rotations: Rotation, + npossible: i32, + possible: [^]RROutput, +} + +XRRMonitorInfo :: struct { + name: Atom, + primary: b32, + automatic: b32, + noutput: i32, + x: i32, + y: i32, + width: i32, + height: i32, + mwidth: i32, + mheight: i32, + outputs: [^]RROutput, +} + +/* ---- X11/extensions/XInput2.h ---------------------------------------------------------*/ + +XIEventType :: enum i32 { + DeviceChanged = 1, + KeyPress, + KeyRelease, + ButtonPress, + ButtonRelease, + Motion, + Enter, + Leave, + FocusIn, + FocusOut, + HierarchyChanged, + Property, + RawKeyPress, + RawKeyRelease, + RawButtonPress, + RawButtonRelease, + RawMotion, + TouchBegin, + TouchUpdate, + TouchEnd, + TouchOwnership, + RawTouchBegin, + RawTouchUpdate, + RawTouchEnd, + BarrierHit, + BarrierLeave, + GesturePinchBegin, + GesturePinchUpdate, + GesturePinchEnd, + GestureSwipeBegin, + GestureSwipeUpdate, + GestureSwipeEnd, + LastEvent = GestureSwipeEnd, +} + +XIEventMask :: struct { + deviceid: i32, + mask_len: i32, + mask: [^]u8, +} + +XIValuatorState :: struct { + mask_len: i32, + mask: [^]u8, + values: [^]f64, +} + +XIRawEvent :: struct { + type: EventType, + serial: u64, + send_event: b32, + display: ^Display, + extension: i32, + evtype: XIEventType, + time: Time, + deviceid: i32, + sourceid: i32, + detail: i32, + flags: i32, + valuators: XIValuatorState, + raw_values: [^]f64, +} +